diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2023-02-14 17:38:45 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-14 17:38:45 +0100 |
commit | d47147fb6ad229b1c039aff9d0959b6e281f4df5 (patch) | |
tree | 6e9e790f2b9bc71b5f0c9c7e64b95cae31579d58 /ext/node/polyfills/internal | |
parent | 1d00bbe47e2ca14e2d2151518e02b2324461a065 (diff) |
feat(ext/node): embed std/node into the snapshot (#17724)
This commit moves "deno_std/node" in "ext/node" crate. The code is
transpiled and snapshotted during the build process.
During the first pass a minimal amount of work was done to create the
snapshot, a lot of code in "ext/node" depends on presence of "Deno"
global. This code will be gradually fixed in the follow up PRs to migrate
it to import relevant APIs from "internal:" modules.
Currently the code from snapshot is not used in any way, and all
Node/npm compatibility still uses code from
"https://deno.land/std/node" (or from the location specified by
"DENO_NODE_COMPAT_URL"). This will also be handled in a follow
up PRs.
---------
Co-authored-by: crowlkats <crowlkats@toaxl.com>
Co-authored-by: Divy Srivastava <dj.srivastava23@gmail.com>
Co-authored-by: Yoshiya Hinosawa <stibium121@gmail.com>
Diffstat (limited to 'ext/node/polyfills/internal')
79 files changed, 26244 insertions, 0 deletions
diff --git a/ext/node/polyfills/internal/assert.mjs b/ext/node/polyfills/internal/assert.mjs new file mode 100644 index 000000000..fcdb32a05 --- /dev/null +++ b/ext/node/polyfills/internal/assert.mjs @@ -0,0 +1,16 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { ERR_INTERNAL_ASSERTION } from "internal:deno_node/polyfills/internal/errors.ts"; + +function assert(value, message) { + if (!value) { + throw new ERR_INTERNAL_ASSERTION(message); + } +} + +function fail(message) { + throw new ERR_INTERNAL_ASSERTION(message); +} + +assert.fail = fail; + +export default assert; diff --git a/ext/node/polyfills/internal/async_hooks.ts b/ext/node/polyfills/internal/async_hooks.ts new file mode 100644 index 000000000..8bf513e46 --- /dev/null +++ b/ext/node/polyfills/internal/async_hooks.ts @@ -0,0 +1,420 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +// deno-lint-ignore camelcase +import * as async_wrap from "internal:deno_node/polyfills/internal_binding/async_wrap.ts"; +import { ERR_ASYNC_CALLBACK } from "internal:deno_node/polyfills/internal/errors.ts"; +export { + asyncIdSymbol, + ownerSymbol, +} from "internal:deno_node/polyfills/internal_binding/symbols.ts"; + +interface ActiveHooks { + array: AsyncHook[]; + // deno-lint-ignore camelcase + call_depth: number; + // deno-lint-ignore camelcase + tmp_array: AsyncHook[] | null; + // deno-lint-ignore camelcase + tmp_fields: number[] | null; +} + +// Properties in active_hooks are used to keep track of the set of hooks being +// executed in case another hook is enabled/disabled. The new set of hooks is +// then restored once the active set of hooks is finished executing. +// deno-lint-ignore camelcase +const active_hooks: ActiveHooks = { + // Array of all AsyncHooks that will be iterated whenever an async event + // fires. Using var instead of (preferably const) in order to assign + // active_hooks.tmp_array if a hook is enabled/disabled during hook + // execution. + array: [], + // Use a counter to track nested calls of async hook callbacks and make sure + // the active_hooks.array isn't altered mid execution. + // deno-lint-ignore camelcase + call_depth: 0, + // Use to temporarily store and updated active_hooks.array if the user + // enables or disables a hook while hooks are being processed. If a hook is + // enabled() or disabled() during hook execution then the current set of + // active hooks is duplicated and set equal to active_hooks.tmp_array. Any + // subsequent changes are on the duplicated array. When all hooks have + // completed executing active_hooks.tmp_array is assigned to + // active_hooks.array. + // deno-lint-ignore camelcase + tmp_array: null, + // Keep track of the field counts held in active_hooks.tmp_array. Because the + // async_hook_fields can't be reassigned, store each uint32 in an array that + // is written back to async_hook_fields when active_hooks.array is restored. + // deno-lint-ignore camelcase + tmp_fields: null, +}; + +export const registerDestroyHook = async_wrap.registerDestroyHook; +const { + // deno-lint-ignore camelcase + async_hook_fields, + // deno-lint-ignore camelcase + asyncIdFields: async_id_fields, + newAsyncId, + constants, +} = async_wrap; +export { newAsyncId }; +const { + kInit, + kBefore, + kAfter, + kDestroy, + kPromiseResolve, + kTotals, + kCheck, + kDefaultTriggerAsyncId, + kStackLength, +} = constants; + +// deno-lint-ignore camelcase +const resource_symbol = Symbol("resource"); +// deno-lint-ignore camelcase +export const async_id_symbol = Symbol("trigger_async_id"); +// deno-lint-ignore camelcase +export const trigger_async_id_symbol = Symbol("trigger_async_id"); +// deno-lint-ignore camelcase +export const init_symbol = Symbol("init"); +// deno-lint-ignore camelcase +export const before_symbol = Symbol("before"); +// deno-lint-ignore camelcase +export const after_symbol = Symbol("after"); +// deno-lint-ignore camelcase +export const destroy_symbol = Symbol("destroy"); +// deno-lint-ignore camelcase +export const promise_resolve_symbol = Symbol("promiseResolve"); + +export const symbols = { + // deno-lint-ignore camelcase + async_id_symbol, + // deno-lint-ignore camelcase + trigger_async_id_symbol, + // deno-lint-ignore camelcase + init_symbol, + // deno-lint-ignore camelcase + before_symbol, + // deno-lint-ignore camelcase + after_symbol, + // deno-lint-ignore camelcase + destroy_symbol, + // deno-lint-ignore camelcase + promise_resolve_symbol, +}; + +// deno-lint-ignore no-explicit-any +function lookupPublicResource(resource: any) { + if (typeof resource !== "object" || resource === null) return resource; + // TODO(addaleax): Merge this with owner_symbol and use it across all + // AsyncWrap instances. + const publicResource = resource[resource_symbol]; + if (publicResource !== undefined) { + return publicResource; + } + return resource; +} + +// Used by C++ to call all init() callbacks. Because some state can be setup +// from C++ there's no need to perform all the same operations as in +// emitInitScript. +function emitInitNative( + asyncId: number, + // deno-lint-ignore no-explicit-any + type: any, + triggerAsyncId: number, + // deno-lint-ignore no-explicit-any + resource: any, +) { + active_hooks.call_depth += 1; + resource = lookupPublicResource(resource); + // Use a single try/catch for all hooks to avoid setting up one per iteration. + try { + for (let i = 0; i < active_hooks.array.length; i++) { + if (typeof active_hooks.array[i][init_symbol] === "function") { + active_hooks.array[i][init_symbol]( + asyncId, + type, + triggerAsyncId, + resource, + ); + } + } + } catch (e) { + throw e; + } finally { + active_hooks.call_depth -= 1; + } + + // Hooks can only be restored if there have been no recursive hook calls. + // Also the active hooks do not need to be restored if enable()/disable() + // weren't called during hook execution, in which case active_hooks.tmp_array + // will be null. + if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) { + restoreActiveHooks(); + } +} + +function getHookArrays(): [AsyncHook[], number[] | Uint32Array] { + if (active_hooks.call_depth === 0) { + return [active_hooks.array, async_hook_fields]; + } + // If this hook is being enabled while in the middle of processing the array + // of currently active hooks then duplicate the current set of active hooks + // and store this there. This shouldn't fire until the next time hooks are + // processed. + if (active_hooks.tmp_array === null) { + storeActiveHooks(); + } + return [active_hooks.tmp_array!, active_hooks.tmp_fields!]; +} + +function storeActiveHooks() { + active_hooks.tmp_array = active_hooks.array.slice(); + // Don't want to make the assumption that kInit to kDestroy are indexes 0 to + // 4. So do this the long way. + active_hooks.tmp_fields = []; + copyHooks(active_hooks.tmp_fields, async_hook_fields); +} + +function copyHooks( + destination: number[] | Uint32Array, + source: number[] | Uint32Array, +) { + destination[kInit] = source[kInit]; + destination[kBefore] = source[kBefore]; + destination[kAfter] = source[kAfter]; + destination[kDestroy] = source[kDestroy]; + destination[kPromiseResolve] = source[kPromiseResolve]; +} + +// Then restore the correct hooks array in case any hooks were added/removed +// during hook callback execution. +function restoreActiveHooks() { + active_hooks.array = active_hooks.tmp_array!; + copyHooks(async_hook_fields, active_hooks.tmp_fields!); + + active_hooks.tmp_array = null; + active_hooks.tmp_fields = null; +} + +// deno-lint-ignore no-unused-vars +let wantPromiseHook = false; +function enableHooks() { + async_hook_fields[kCheck] += 1; + + // TODO(kt3k): Uncomment this + // setCallbackTrampoline(callbackTrampoline); +} + +function disableHooks() { + async_hook_fields[kCheck] -= 1; + + wantPromiseHook = false; + + // TODO(kt3k): Uncomment the below + // setCallbackTrampoline(); + + // Delay the call to `disablePromiseHook()` because we might currently be + // between the `before` and `after` calls of a Promise. + // TODO(kt3k): Uncomment the below + // enqueueMicrotask(disablePromiseHookIfNecessary); +} + +// Return the triggerAsyncId meant for the constructor calling it. It's up to +// the user to safeguard this call and make sure it's zero'd out when the +// constructor is complete. +export function getDefaultTriggerAsyncId() { + const defaultTriggerAsyncId = + async_id_fields[async_wrap.UidFields.kDefaultTriggerAsyncId]; + // If defaultTriggerAsyncId isn't set, use the executionAsyncId + if (defaultTriggerAsyncId < 0) { + return async_id_fields[async_wrap.UidFields.kExecutionAsyncId]; + } + return defaultTriggerAsyncId; +} + +export function defaultTriggerAsyncIdScope( + triggerAsyncId: number | undefined, + // deno-lint-ignore no-explicit-any + block: (...arg: any[]) => void, + ...args: unknown[] +) { + if (triggerAsyncId === undefined) { + return block.apply(null, args); + } + // CHECK(NumberIsSafeInteger(triggerAsyncId)) + // CHECK(triggerAsyncId > 0) + const oldDefaultTriggerAsyncId = async_id_fields[kDefaultTriggerAsyncId]; + async_id_fields[kDefaultTriggerAsyncId] = triggerAsyncId; + + try { + return block.apply(null, args); + } finally { + async_id_fields[kDefaultTriggerAsyncId] = oldDefaultTriggerAsyncId; + } +} + +function hasHooks(key: number) { + return async_hook_fields[key] > 0; +} + +export function enabledHooksExist() { + return hasHooks(kCheck); +} + +export function initHooksExist() { + return hasHooks(kInit); +} + +export function afterHooksExist() { + return hasHooks(kAfter); +} + +export function destroyHooksExist() { + return hasHooks(kDestroy); +} + +export function promiseResolveHooksExist() { + return hasHooks(kPromiseResolve); +} + +function emitInitScript( + asyncId: number, + // deno-lint-ignore no-explicit-any + type: any, + triggerAsyncId: number, + // deno-lint-ignore no-explicit-any + resource: any, +) { + // Short circuit all checks for the common case. Which is that no hooks have + // been set. Do this to remove performance impact for embedders (and core). + if (!hasHooks(kInit)) { + return; + } + + if (triggerAsyncId === null) { + triggerAsyncId = getDefaultTriggerAsyncId(); + } + + emitInitNative(asyncId, type, triggerAsyncId, resource); +} +export { emitInitScript as emitInit }; + +export function hasAsyncIdStack() { + return hasHooks(kStackLength); +} + +export { constants }; + +type Fn = (...args: unknown[]) => unknown; + +export class AsyncHook { + [init_symbol]: Fn; + [before_symbol]: Fn; + [after_symbol]: Fn; + [destroy_symbol]: Fn; + [promise_resolve_symbol]: Fn; + + constructor({ + init, + before, + after, + destroy, + promiseResolve, + }: { + init: Fn; + before: Fn; + after: Fn; + destroy: Fn; + promiseResolve: Fn; + }) { + if (init !== undefined && typeof init !== "function") { + throw new ERR_ASYNC_CALLBACK("hook.init"); + } + if (before !== undefined && typeof before !== "function") { + throw new ERR_ASYNC_CALLBACK("hook.before"); + } + if (after !== undefined && typeof after !== "function") { + throw new ERR_ASYNC_CALLBACK("hook.after"); + } + if (destroy !== undefined && typeof destroy !== "function") { + throw new ERR_ASYNC_CALLBACK("hook.destroy"); + } + if (promiseResolve !== undefined && typeof promiseResolve !== "function") { + throw new ERR_ASYNC_CALLBACK("hook.promiseResolve"); + } + + this[init_symbol] = init; + this[before_symbol] = before; + this[after_symbol] = after; + this[destroy_symbol] = destroy; + this[promise_resolve_symbol] = promiseResolve; + } + + enable() { + // The set of callbacks for a hook should be the same regardless of whether + // enable()/disable() are run during their execution. The following + // references are reassigned to the tmp arrays if a hook is currently being + // processed. + // deno-lint-ignore camelcase + const { 0: hooks_array, 1: hook_fields } = getHookArrays(); + + // Each hook is only allowed to be added once. + if (hooks_array.includes(this)) { + return this; + } + + // deno-lint-ignore camelcase + const prev_kTotals = hook_fields[kTotals]; + + // createHook() has already enforced that the callbacks are all functions, + // so here simply increment the count of whether each callbacks exists or + // not. + hook_fields[kTotals] = hook_fields[kInit] += +!!this[init_symbol]; + hook_fields[kTotals] += hook_fields[kBefore] += +!!this[before_symbol]; + hook_fields[kTotals] += hook_fields[kAfter] += +!!this[after_symbol]; + hook_fields[kTotals] += hook_fields[kDestroy] += +!!this[destroy_symbol]; + hook_fields[kTotals] += hook_fields[kPromiseResolve] += + +!!this[promise_resolve_symbol]; + hooks_array.push(this); + + if (prev_kTotals === 0 && hook_fields[kTotals] > 0) { + enableHooks(); + } + + // TODO(kt3k): Uncomment the below + // updatePromiseHookMode(); + + return this; + } + + disable() { + // deno-lint-ignore camelcase + const { 0: hooks_array, 1: hook_fields } = getHookArrays(); + + const index = hooks_array.indexOf(this); + if (index === -1) { + return this; + } + + // deno-lint-ignore camelcase + const prev_kTotals = hook_fields[kTotals]; + + hook_fields[kTotals] = hook_fields[kInit] -= +!!this[init_symbol]; + hook_fields[kTotals] += hook_fields[kBefore] -= +!!this[before_symbol]; + hook_fields[kTotals] += hook_fields[kAfter] -= +!!this[after_symbol]; + hook_fields[kTotals] += hook_fields[kDestroy] -= +!!this[destroy_symbol]; + hook_fields[kTotals] += hook_fields[kPromiseResolve] -= + +!!this[promise_resolve_symbol]; + hooks_array.splice(index, 1); + + if (prev_kTotals > 0 && hook_fields[kTotals] === 0) { + disableHooks(); + } + + return this; + } +} diff --git a/ext/node/polyfills/internal/blob.mjs b/ext/node/polyfills/internal/blob.mjs new file mode 100644 index 000000000..1b685fad4 --- /dev/null +++ b/ext/node/polyfills/internal/blob.mjs @@ -0,0 +1,7 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Node's implementation checks for a symbol they put in the blob prototype +// Since the implementation of Blob is Deno's, the only option is to check the +// objects constructor +export function isBlob(object) { + return object instanceof Blob; +} diff --git a/ext/node/polyfills/internal/buffer.d.ts b/ext/node/polyfills/internal/buffer.d.ts new file mode 100644 index 000000000..638674467 --- /dev/null +++ b/ext/node/polyfills/internal/buffer.d.ts @@ -0,0 +1,2074 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright DefinitelyTyped contributors. All rights reserved. MIT license. + +/** + * One of many allowed encodings for the buffer content + * - ascii + * - base64 + * - base64url + * - binary + * - hex + * - latin1 + * - ucs-2 + * - ucs2 + * - utf-8 + * - utf16le + * - utf8 + */ +type Encoding = unknown; + +type WithImplicitCoercion<T> = + | T + | { + valueOf(): T; + }; + +/** + * `Buffer` objects are used to represent a fixed-length sequence of bytes. Many + * Node.js APIs support `Buffer`s. + * + * The `Buffer` class is a subclass of JavaScript's [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) class and + * extends it with methods that cover additional use cases. Node.js APIs accept + * plain [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) s wherever `Buffer`s are supported as well. + * + * While the `Buffer` class is available within the global scope, it is still + * recommended to explicitly reference it via an import or require statement. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Creates a zero-filled Buffer of length 10. + * const buf1 = Buffer.alloc(10); + * + * // Creates a Buffer of length 10, + * // filled with bytes which all have the value `1`. + * const buf2 = Buffer.alloc(10, 1); + * + * // Creates an uninitialized buffer of length 10. + * // This is faster than calling Buffer.alloc() but the returned + * // Buffer instance might contain old data that needs to be + * // overwritten using fill(), write(), or other functions that fill the Buffer's + * // contents. + * const buf3 = Buffer.allocUnsafe(10); + * + * // Creates a Buffer containing the bytes [1, 2, 3]. + * const buf4 = Buffer.from([1, 2, 3]); + * + * // Creates a Buffer containing the bytes [1, 1, 1, 1] â the entries + * // are all truncated using `(value & 255)` to fit into the range 0â255. + * const buf5 = Buffer.from([257, 257.5, -255, '1']); + * + * // Creates a Buffer containing the UTF-8-encoded bytes for the string 'tĂ©st': + * // [0x74, 0xc3, 0xa9, 0x73, 0x74] (in hexadecimal notation) + * // [116, 195, 169, 115, 116] (in decimal notation) + * const buf6 = Buffer.from('tĂ©st'); + * + * // Creates a Buffer containing the Latin-1 bytes [0x74, 0xe9, 0x73, 0x74]. + * const buf7 = Buffer.from('tĂ©st', 'latin1'); + * ``` + * @see [source](https://github.com/nodejs/node/blob/v16.9.0/lib/buffer.js) + */ +export class Buffer extends Uint8Array { + /** + * Allocates a new buffer containing the given {str}. + * + * @param str String to store in buffer. + * @param encoding encoding to use, optional. Default is 'utf8' + * @deprecated since v10.0.0 - Use `Buffer.from(string[, encoding])` instead. + */ + constructor(str: string, encoding?: Encoding); + /** + * Allocates a new buffer of {size} octets. + * + * @param size count of octets to allocate. + * @deprecated since v10.0.0 - Use `Buffer.alloc()` instead (also see `Buffer.allocUnsafe()`). + */ + constructor(size: number); + /** + * Allocates a new buffer containing the given {array} of octets. + * + * @param array The octets to store. + * @deprecated since v10.0.0 - Use `Buffer.from(array)` instead. + */ + constructor(array: Uint8Array); + /** + * Produces a Buffer backed by the same allocated memory as + * the given {ArrayBuffer}/{SharedArrayBuffer}. + * + * @param arrayBuffer The ArrayBuffer with which to share memory. + * @deprecated since v10.0.0 - Use `Buffer.from(arrayBuffer[, byteOffset[, length]])` instead. + */ + constructor(arrayBuffer: ArrayBuffer | SharedArrayBuffer); + /** + * Allocates a new buffer containing the given {array} of octets. + * + * @param array The octets to store. + * @deprecated since v10.0.0 - Use `Buffer.from(array)` instead. + */ + constructor(array: ReadonlyArray<unknown>); + /** + * Copies the passed {buffer} data onto a new {Buffer} instance. + * + * @param buffer The buffer to copy. + * @deprecated since v10.0.0 - Use `Buffer.from(buffer)` instead. + */ + constructor(buffer: Buffer); + /** + * Allocates a new `Buffer` using an `array` of bytes in the range `0` â `255`. + * Array entries outside that range will be truncated to fit into it. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Creates a new Buffer containing the UTF-8 bytes of the string 'buffer'. + * const buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]); + * ``` + * + * A `TypeError` will be thrown if `array` is not an `Array` or another type + * appropriate for `Buffer.from()` variants. + * + * `Buffer.from(array)` and `Buffer.from(string)` may also use the internal`Buffer` pool like `Buffer.allocUnsafe()` does. + * @since v5.10.0 + */ + static from( + arrayBuffer: WithImplicitCoercion<ArrayBuffer | SharedArrayBuffer>, + byteOffset?: number, + length?: number, + ): Buffer; + /** + * Creates a new Buffer using the passed {data} + * @param data data to create a new Buffer + */ + static from(data: Uint8Array | ReadonlyArray<number>): Buffer; + static from( + data: WithImplicitCoercion<Uint8Array | ReadonlyArray<number> | string>, + ): Buffer; + /** + * Creates a new Buffer containing the given JavaScript string {str}. + * If provided, the {encoding} parameter identifies the character encoding. + * If not provided, {encoding} defaults to 'utf8'. + */ + static from( + str: + | WithImplicitCoercion<string> + | { + [Symbol.toPrimitive](hint: "string"): string; + }, + encoding?: Encoding, + ): Buffer; + /** + * Creates a new Buffer using the passed {data} + * @param values to create a new Buffer + */ + static of(...items: number[]): Buffer; + /** + * Returns `true` if `obj` is a `Buffer`, `false` otherwise. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * Buffer.isBuffer(Buffer.alloc(10)); // true + * Buffer.isBuffer(Buffer.from('foo')); // true + * Buffer.isBuffer('a string'); // false + * Buffer.isBuffer([]); // false + * Buffer.isBuffer(new Uint8Array(1024)); // false + * ``` + * @since v0.1.101 + */ + static isBuffer(obj: unknown): obj is Buffer; + /** + * Returns `true` if `encoding` is the name of a supported character encoding, + * or `false` otherwise. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * console.log(Buffer.isEncoding('utf8')); + * // Prints: true + * + * console.log(Buffer.isEncoding('hex')); + * // Prints: true + * + * console.log(Buffer.isEncoding('utf/8')); + * // Prints: false + * + * console.log(Buffer.isEncoding('')); + * // Prints: false + * ``` + * @since v0.9.1 + * @param encoding A character encoding name to check. + */ + static isEncoding(encoding: string): boolean; + /** + * Returns the byte length of a string when encoded using `encoding`. + * This is not the same as [`String.prototype.length`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length), which does not account + * for the encoding that is used to convert the string into bytes. + * + * For `'base64'`, `'base64url'`, and `'hex'`, this function assumes valid input. + * For strings that contain non-base64/hex-encoded data (e.g. whitespace), the + * return value might be greater than the length of a `Buffer` created from the + * string. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const str = '\u00bd + \u00bc = \u00be'; + * + * console.log(`${str}: ${str.length} characters, ` + + * `${Buffer.byteLength(str, 'utf8')} bytes`); + * // Prints: œ + ÂŒ = Ÿ: 9 characters, 12 bytes + * ``` + * + * When `string` is a + * `Buffer`/[`DataView`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView)/[`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/- + * Reference/Global_Objects/TypedArray)/[`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)/[`SharedArrayBuffer`](https://develop- + * er.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer), the byte length as reported by `.byteLength`is returned. + * @since v0.1.90 + * @param string A value to calculate the length of. + * @param [encoding='utf8'] If `string` is a string, this is its encoding. + * @return The number of bytes contained within `string`. + */ + static byteLength( + string: + | string + | ArrayBufferView + | ArrayBuffer + | SharedArrayBuffer, + encoding?: Encoding, + ): number; + /** + * Returns a new `Buffer` which is the result of concatenating all the `Buffer`instances in the `list` together. + * + * If the list has no items, or if the `totalLength` is 0, then a new zero-length`Buffer` is returned. + * + * If `totalLength` is not provided, it is calculated from the `Buffer` instances + * in `list` by adding their lengths. + * + * If `totalLength` is provided, it is coerced to an unsigned integer. If the + * combined length of the `Buffer`s in `list` exceeds `totalLength`, the result is + * truncated to `totalLength`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Create a single `Buffer` from a list of three `Buffer` instances. + * + * const buf1 = Buffer.alloc(10); + * const buf2 = Buffer.alloc(14); + * const buf3 = Buffer.alloc(18); + * const totalLength = buf1.length + buf2.length + buf3.length; + * + * console.log(totalLength); + * // Prints: 42 + * + * const bufA = Buffer.concat([buf1, buf2, buf3], totalLength); + * + * console.log(bufA); + * // Prints: <Buffer 00 00 00 00 ...> + * console.log(bufA.length); + * // Prints: 42 + * ``` + * + * `Buffer.concat()` may also use the internal `Buffer` pool like `Buffer.allocUnsafe()` does. + * @since v0.7.11 + * @param list List of `Buffer` or {@link Uint8Array} instances to concatenate. + * @param totalLength Total length of the `Buffer` instances in `list` when concatenated. + */ + static concat( + list: ReadonlyArray<Uint8Array>, + totalLength?: number, + ): Buffer; + /** + * Compares `buf1` to `buf2`, typically for the purpose of sorting arrays of`Buffer` instances. This is equivalent to calling `buf1.compare(buf2)`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf1 = Buffer.from('1234'); + * const buf2 = Buffer.from('0123'); + * const arr = [buf1, buf2]; + * + * console.log(arr.sort(Buffer.compare)); + * // Prints: [ <Buffer 30 31 32 33>, <Buffer 31 32 33 34> ] + * // (This result is equal to: [buf2, buf1].) + * ``` + * @since v0.11.13 + * @return Either `-1`, `0`, or `1`, depending on the result of the comparison. See `compare` for details. + */ + static compare(buf1: Uint8Array, buf2: Uint8Array): number; + /** + * Allocates a new `Buffer` of `size` bytes. If `fill` is `undefined`, the`Buffer` will be zero-filled. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.alloc(5); + * + * console.log(buf); + * // Prints: <Buffer 00 00 00 00 00> + * ``` + * + * If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_INVALID_ARG_VALUE` is thrown. + * + * If `fill` is specified, the allocated `Buffer` will be initialized by calling `buf.fill(fill)`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.alloc(5, 'a'); + * + * console.log(buf); + * // Prints: <Buffer 61 61 61 61 61> + * ``` + * + * If both `fill` and `encoding` are specified, the allocated `Buffer` will be + * initialized by calling `buf.fill(fill, encoding)`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.alloc(11, 'aGVsbG8gd29ybGQ=', 'base64'); + * + * console.log(buf); + * // Prints: <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64> + * ``` + * + * Calling `Buffer.alloc()` can be measurably slower than the alternative `Buffer.allocUnsafe()` but ensures that the newly created `Buffer` instance + * contents will never contain sensitive data from previous allocations, including + * data that might not have been allocated for `Buffer`s. + * + * A `TypeError` will be thrown if `size` is not a number. + * @since v5.10.0 + * @param size The desired length of the new `Buffer`. + * @param [fill=0] A value to pre-fill the new `Buffer` with. + * @param [encoding='utf8'] If `fill` is a string, this is its encoding. + */ + static alloc( + size: number, + fill?: string | Uint8Array | number, + encoding?: Encoding, + ): Buffer; + /** + * Allocates a new `Buffer` of `size` bytes. If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_INVALID_ARG_VALUE` is thrown. + * + * The underlying memory for `Buffer` instances created in this way is _not_ + * _initialized_. The contents of the newly created `Buffer` are unknown and_may contain sensitive data_. Use `Buffer.alloc()` instead to initialize`Buffer` instances with zeroes. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(10); + * + * console.log(buf); + * // Prints (contents may vary): <Buffer a0 8b 28 3f 01 00 00 00 50 32> + * + * buf.fill(0); + * + * console.log(buf); + * // Prints: <Buffer 00 00 00 00 00 00 00 00 00 00> + * ``` + * + * A `TypeError` will be thrown if `size` is not a number. + * + * The `Buffer` module pre-allocates an internal `Buffer` instance of + * size `Buffer.poolSize` that is used as a pool for the fast allocation of new`Buffer` instances created using `Buffer.allocUnsafe()`,`Buffer.from(array)`, `Buffer.concat()`, and the + * deprecated`new Buffer(size)` constructor only when `size` is less than or equal + * to `Buffer.poolSize >> 1` (floor of `Buffer.poolSize` divided by two). + * + * Use of this pre-allocated internal memory pool is a key difference between + * calling `Buffer.alloc(size, fill)` vs. `Buffer.allocUnsafe(size).fill(fill)`. + * Specifically, `Buffer.alloc(size, fill)` will _never_ use the internal `Buffer`pool, while `Buffer.allocUnsafe(size).fill(fill)`_will_ use the internal`Buffer` pool if `size` is less + * than or equal to half `Buffer.poolSize`. The + * difference is subtle but can be important when an application requires the + * additional performance that `Buffer.allocUnsafe()` provides. + * @since v5.10.0 + * @param size The desired length of the new `Buffer`. + */ + static allocUnsafe(size: number): Buffer; + /** + * Allocates a new `Buffer` of `size` bytes. If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_INVALID_ARG_VALUE` is thrown. A zero-length `Buffer` is created + * if `size` is 0. + * + * The underlying memory for `Buffer` instances created in this way is _not_ + * _initialized_. The contents of the newly created `Buffer` are unknown and_may contain sensitive data_. Use `buf.fill(0)` to initialize + * such `Buffer` instances with zeroes. + * + * When using `Buffer.allocUnsafe()` to allocate new `Buffer` instances, + * allocations under 4 KB are sliced from a single pre-allocated `Buffer`. This + * allows applications to avoid the garbage collection overhead of creating many + * individually allocated `Buffer` instances. This approach improves both + * performance and memory usage by eliminating the need to track and clean up as + * many individual `ArrayBuffer` objects. + * + * However, in the case where a developer may need to retain a small chunk of + * memory from a pool for an indeterminate amount of time, it may be appropriate + * to create an un-pooled `Buffer` instance using `Buffer.allocUnsafeSlow()` and + * then copying out the relevant bits. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Need to keep around a few small chunks of memory. + * const store = []; + * + * socket.on('readable', () => { + * let data; + * while (null !== (data = readable.read())) { + * // Allocate for retained data. + * const sb = Buffer.allocUnsafeSlow(10); + * + * // Copy the data into the new allocation. + * data.copy(sb, 0, 0, 10); + * + * store.push(sb); + * } + * }); + * ``` + * + * A `TypeError` will be thrown if `size` is not a number. + * @since v5.12.0 + * @param size The desired length of the new `Buffer`. + */ + static allocUnsafeSlow(size: number): Buffer; + // /** + // * This is the size (in bytes) of pre-allocated internal `Buffer` instances used + // * for pooling. This value may be modified. + // * @since v0.11.3 + // */ + // static poolSize: number; + + /** + * Writes `string` to `buf` at `offset` according to the character encoding in`encoding`. The `length` parameter is the number of bytes to write. If `buf` did + * not contain enough space to fit the entire string, only part of `string` will be + * written. However, partially encoded characters will not be written. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.alloc(256); + * + * const len = buf.write('\u00bd + \u00bc = \u00be', 0); + * + * console.log(`${len} bytes: ${buf.toString('utf8', 0, len)}`); + * // Prints: 12 bytes: œ + ÂŒ = Ÿ + * + * const buffer = Buffer.alloc(10); + * + * const length = buffer.write('abcd', 8); + * + * console.log(`${length} bytes: ${buffer.toString('utf8', 8, 10)}`); + * // Prints: 2 bytes : ab + * ``` + * @since v0.1.90 + * @param string String to write to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write `string`. + * @param [length=buf.length - offset] Maximum number of bytes to write (written bytes will not exceed `buf.length - offset`). + * @param [encoding='utf8'] The character encoding of `string`. + * @return Number of bytes written. + */ + write(string: string, encoding?: Encoding): number; + write(string: string, offset: number, encoding?: Encoding): number; + write( + string: string, + offset: number, + length: number, + encoding?: Encoding, + ): number; + /** + * Decodes `buf` to a string according to the specified character encoding in`encoding`. `start` and `end` may be passed to decode only a subset of `buf`. + * + * If `encoding` is `'utf8'` and a byte sequence in the input is not valid UTF-8, + * then each invalid byte is replaced with the replacement character `U+FFFD`. + * + * The maximum length of a string instance (in UTF-16 code units) is available + * as {@link constants.MAX_STRING_LENGTH}. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf1 = Buffer.allocUnsafe(26); + * + * for (let i = 0; i < 26; i++) { + * // 97 is the decimal ASCII value for 'a'. + * buf1[i] = i + 97; + * } + * + * console.log(buf1.toString('utf8')); + * // Prints: abcdefghijklmnopqrstuvwxyz + * console.log(buf1.toString('utf8', 0, 5)); + * // Prints: abcde + * + * const buf2 = Buffer.from('tĂ©st'); + * + * console.log(buf2.toString('hex')); + * // Prints: 74c3a97374 + * console.log(buf2.toString('utf8', 0, 3)); + * // Prints: tĂ© + * console.log(buf2.toString(undefined, 0, 3)); + * // Prints: tĂ© + * ``` + * @since v0.1.90 + * @param [encoding='utf8'] The character encoding to use. + * @param [start=0] The byte offset to start decoding at. + * @param [end=buf.length] The byte offset to stop decoding at (not inclusive). + */ + toString(encoding?: Encoding, start?: number, end?: number): string; + /** + * Returns a JSON representation of `buf`. [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) implicitly calls + * this function when stringifying a `Buffer` instance. + * + * `Buffer.from()` accepts objects in the format returned from this method. + * In particular, `Buffer.from(buf.toJSON())` works like `Buffer.from(buf)`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]); + * const json = JSON.stringify(buf); + * + * console.log(json); + * // Prints: {"type":"Buffer","data":[1,2,3,4,5]} + * + * const copy = JSON.parse(json, (key, value) => { + * return value && value.type === 'Buffer' ? + * Buffer.from(value) : + * value; + * }); + * + * console.log(copy); + * // Prints: <Buffer 01 02 03 04 05> + * ``` + * @since v0.9.2 + */ + toJSON(): { + type: "Buffer"; + data: number[]; + }; + /** + * Returns `true` if both `buf` and `otherBuffer` have exactly the same bytes,`false` otherwise. Equivalent to `buf.compare(otherBuffer) === 0`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf1 = Buffer.from('ABC'); + * const buf2 = Buffer.from('414243', 'hex'); + * const buf3 = Buffer.from('ABCD'); + * + * console.log(buf1.equals(buf2)); + * // Prints: true + * console.log(buf1.equals(buf3)); + * // Prints: false + * ``` + * @since v0.11.13 + * @param otherBuffer A `Buffer` or {@link Uint8Array} with which to compare `buf`. + */ + equals(otherBuffer: Uint8Array): boolean; + /** + * Compares `buf` with `target` and returns a number indicating whether `buf`comes before, after, or is the same as `target` in sort order. + * Comparison is based on the actual sequence of bytes in each `Buffer`. + * + * * `0` is returned if `target` is the same as `buf` + * * `1` is returned if `target` should come _before_`buf` when sorted. + * * `-1` is returned if `target` should come _after_`buf` when sorted. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf1 = Buffer.from('ABC'); + * const buf2 = Buffer.from('BCD'); + * const buf3 = Buffer.from('ABCD'); + * + * console.log(buf1.compare(buf1)); + * // Prints: 0 + * console.log(buf1.compare(buf2)); + * // Prints: -1 + * console.log(buf1.compare(buf3)); + * // Prints: -1 + * console.log(buf2.compare(buf1)); + * // Prints: 1 + * console.log(buf2.compare(buf3)); + * // Prints: 1 + * console.log([buf1, buf2, buf3].sort(Buffer.compare)); + * // Prints: [ <Buffer 41 42 43>, <Buffer 41 42 43 44>, <Buffer 42 43 44> ] + * // (This result is equal to: [buf1, buf3, buf2].) + * ``` + * + * The optional `targetStart`, `targetEnd`, `sourceStart`, and `sourceEnd`arguments can be used to limit the comparison to specific ranges within `target`and `buf` respectively. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf1 = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9]); + * const buf2 = Buffer.from([5, 6, 7, 8, 9, 1, 2, 3, 4]); + * + * console.log(buf1.compare(buf2, 5, 9, 0, 4)); + * // Prints: 0 + * console.log(buf1.compare(buf2, 0, 6, 4)); + * // Prints: -1 + * console.log(buf1.compare(buf2, 5, 6, 5)); + * // Prints: 1 + * ``` + * + * `ERR_OUT_OF_RANGE` is thrown if `targetStart < 0`, `sourceStart < 0`,`targetEnd > target.byteLength`, or `sourceEnd > source.byteLength`. + * @since v0.11.13 + * @param target A `Buffer` or {@link Uint8Array} with which to compare `buf`. + * @param [targetStart=0] The offset within `target` at which to begin comparison. + * @param [targetEnd=target.length] The offset within `target` at which to end comparison (not inclusive). + * @param [sourceStart=0] The offset within `buf` at which to begin comparison. + * @param [sourceEnd=buf.length] The offset within `buf` at which to end comparison (not inclusive). + */ + compare( + target: Uint8Array, + targetStart?: number, + targetEnd?: number, + sourceStart?: number, + sourceEnd?: number, + ): number; + /** + * Copies data from a region of `buf` to a region in `target`, even if the `target`memory region overlaps with `buf`. + * + * [`TypedArray.prototype.set()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/set) performs the same operation, and is available + * for all TypedArrays, including Node.js `Buffer`s, although it takes + * different function arguments. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Create two `Buffer` instances. + * const buf1 = Buffer.allocUnsafe(26); + * const buf2 = Buffer.allocUnsafe(26).fill('!'); + * + * for (let i = 0; i < 26; i++) { + * // 97 is the decimal ASCII value for 'a'. + * buf1[i] = i + 97; + * } + * + * // Copy `buf1` bytes 16 through 19 into `buf2` starting at byte 8 of `buf2`. + * buf1.copy(buf2, 8, 16, 20); + * // This is equivalent to: + * // buf2.set(buf1.subarray(16, 20), 8); + * + * console.log(buf2.toString('ascii', 0, 25)); + * // Prints: !!!!!!!!qrst!!!!!!!!!!!!! + * ``` + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Create a `Buffer` and copy data from one region to an overlapping region + * // within the same `Buffer`. + * + * const buf = Buffer.allocUnsafe(26); + * + * for (let i = 0; i < 26; i++) { + * // 97 is the decimal ASCII value for 'a'. + * buf[i] = i + 97; + * } + * + * buf.copy(buf, 0, 4, 10); + * + * console.log(buf.toString()); + * // Prints: efghijghijklmnopqrstuvwxyz + * ``` + * @since v0.1.90 + * @param target A `Buffer` or {@link Uint8Array} to copy into. + * @param [targetStart=0] The offset within `target` at which to begin writing. + * @param [sourceStart=0] The offset within `buf` from which to begin copying. + * @param [sourceEnd=buf.length] The offset within `buf` at which to stop copying (not inclusive). + * @return The number of bytes copied. + */ + copy( + target: Uint8Array, + targetStart?: number, + sourceStart?: number, + sourceEnd?: number, + ): number; + /** + * Returns a new `Buffer` that references the same memory as the original, but + * offset and cropped by the `start` and `end` indices. + * + * This is the same behavior as `buf.subarray()`. + * + * This method is not compatible with the `Uint8Array.prototype.slice()`, + * which is a superclass of `Buffer`. To copy the slice, use`Uint8Array.prototype.slice()`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from('buffer'); + * + * const copiedBuf = Uint8Array.prototype.slice.call(buf); + * copiedBuf[0]++; + * console.log(copiedBuf.toString()); + * // Prints: cuffer + * + * console.log(buf.toString()); + * // Prints: buffer + * ``` + * @since v0.3.0 + * @param [start=0] Where the new `Buffer` will start. + * @param [end=buf.length] Where the new `Buffer` will end (not inclusive). + */ + slice(start?: number, end?: number): Buffer; + /** + * Returns a new `Buffer` that references the same memory as the original, but + * offset and cropped by the `start` and `end` indices. + * + * Specifying `end` greater than `buf.length` will return the same result as + * that of `end` equal to `buf.length`. + * + * This method is inherited from [`TypedArray.prototype.subarray()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/subarray). + * + * Modifying the new `Buffer` slice will modify the memory in the original `Buffer`because the allocated memory of the two objects overlap. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Create a `Buffer` with the ASCII alphabet, take a slice, and modify one byte + * // from the original `Buffer`. + * + * const buf1 = Buffer.allocUnsafe(26); + * + * for (let i = 0; i < 26; i++) { + * // 97 is the decimal ASCII value for 'a'. + * buf1[i] = i + 97; + * } + * + * const buf2 = buf1.subarray(0, 3); + * + * console.log(buf2.toString('ascii', 0, buf2.length)); + * // Prints: abc + * + * buf1[0] = 33; + * + * console.log(buf2.toString('ascii', 0, buf2.length)); + * // Prints: !bc + * ``` + * + * Specifying negative indexes causes the slice to be generated relative to the + * end of `buf` rather than the beginning. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from('buffer'); + * + * console.log(buf.subarray(-6, -1).toString()); + * // Prints: buffe + * // (Equivalent to buf.subarray(0, 5).) + * + * console.log(buf.subarray(-6, -2).toString()); + * // Prints: buff + * // (Equivalent to buf.subarray(0, 4).) + * + * console.log(buf.subarray(-5, -2).toString()); + * // Prints: uff + * // (Equivalent to buf.subarray(1, 4).) + * ``` + * @since v3.0.0 + * @param [start=0] Where the new `Buffer` will start. + * @param [end=buf.length] Where the new `Buffer` will end (not inclusive). + */ + subarray(start?: number, end?: number): Buffer; + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. + * + * `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(8); + * + * buf.writeBigInt64BE(0x0102030405060708n, 0); + * + * console.log(buf); + * // Prints: <Buffer 01 02 03 04 05 06 07 08> + * ``` + * @since v12.0.0, v10.20.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy: `0 <= offset <= buf.length - 8`. + * @return `offset` plus the number of bytes written. + */ + writeBigInt64BE(value: bigint, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. + * + * `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(8); + * + * buf.writeBigInt64LE(0x0102030405060708n, 0); + * + * console.log(buf); + * // Prints: <Buffer 08 07 06 05 04 03 02 01> + * ``` + * @since v12.0.0, v10.20.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy: `0 <= offset <= buf.length - 8`. + * @return `offset` plus the number of bytes written. + */ + writeBigInt64LE(value: bigint, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. + * + * This function is also available under the `writeBigUint64BE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(8); + * + * buf.writeBigUInt64BE(0xdecafafecacefaden, 0); + * + * console.log(buf); + * // Prints: <Buffer de ca fa fe ca ce fa de> + * ``` + * @since v12.0.0, v10.20.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy: `0 <= offset <= buf.length - 8`. + * @return `offset` plus the number of bytes written. + */ + writeBigUInt64BE(value: bigint, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as little-endian + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(8); + * + * buf.writeBigUInt64LE(0xdecafafecacefaden, 0); + * + * console.log(buf); + * // Prints: <Buffer de fa ce ca fe fa ca de> + * ``` + * + * This function is also available under the `writeBigUint64LE` alias. + * @since v12.0.0, v10.20.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy: `0 <= offset <= buf.length - 8`. + * @return `offset` plus the number of bytes written. + */ + writeBigUInt64LE(value: bigint, offset?: number): number; + /** + * Writes `byteLength` bytes of `value` to `buf` at the specified `offset`as little-endian. Supports up to 48 bits of accuracy. Behavior is undefined + * when `value` is anything other than an unsigned integer. + * + * This function is also available under the `writeUintLE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(6); + * + * buf.writeUIntLE(0x1234567890ab, 0, 6); + * + * console.log(buf); + * // Prints: <Buffer ab 90 78 56 34 12> + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param offset Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to write. Must satisfy `0 < byteLength <= 6`. + * @return `offset` plus the number of bytes written. + */ + writeUIntLE(value: number, offset: number, byteLength: number): number; + /** + * Writes `byteLength` bytes of `value` to `buf` at the specified `offset`as big-endian. Supports up to 48 bits of accuracy. Behavior is undefined + * when `value` is anything other than an unsigned integer. + * + * This function is also available under the `writeUintBE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(6); + * + * buf.writeUIntBE(0x1234567890ab, 0, 6); + * + * console.log(buf); + * // Prints: <Buffer 12 34 56 78 90 ab> + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param offset Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to write. Must satisfy `0 < byteLength <= 6`. + * @return `offset` plus the number of bytes written. + */ + writeUIntBE(value: number, offset: number, byteLength: number): number; + /** + * Writes `byteLength` bytes of `value` to `buf` at the specified `offset`as little-endian. Supports up to 48 bits of accuracy. Behavior is undefined + * when `value` is anything other than a signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(6); + * + * buf.writeIntLE(0x1234567890ab, 0, 6); + * + * console.log(buf); + * // Prints: <Buffer ab 90 78 56 34 12> + * ``` + * @since v0.11.15 + * @param value Number to be written to `buf`. + * @param offset Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to write. Must satisfy `0 < byteLength <= 6`. + * @return `offset` plus the number of bytes written. + */ + writeIntLE(value: number, offset: number, byteLength: number): number; + /** + * Writes `byteLength` bytes of `value` to `buf` at the specified `offset`as big-endian. Supports up to 48 bits of accuracy. Behavior is undefined when`value` is anything other than a + * signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(6); + * + * buf.writeIntBE(0x1234567890ab, 0, 6); + * + * console.log(buf); + * // Prints: <Buffer 12 34 56 78 90 ab> + * ``` + * @since v0.11.15 + * @param value Number to be written to `buf`. + * @param offset Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to write. Must satisfy `0 < byteLength <= 6`. + * @return `offset` plus the number of bytes written. + */ + writeIntBE(value: number, offset: number, byteLength: number): number; + /** + * Reads an unsigned, big-endian 64-bit integer from `buf` at the specified`offset`. + * + * This function is also available under the `readBigUint64BE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff]); + * + * console.log(buf.readBigUInt64BE(0)); + * // Prints: 4294967295n + * ``` + * @since v12.0.0, v10.20.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy: `0 <= offset <= buf.length - 8`. + */ + readBigUInt64BE(offset?: number): bigint; + /** + * Reads an unsigned, little-endian 64-bit integer from `buf` at the specified`offset`. + * + * This function is also available under the `readBigUint64LE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff]); + * + * console.log(buf.readBigUInt64LE(0)); + * // Prints: 18446744069414584320n + * ``` + * @since v12.0.0, v10.20.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy: `0 <= offset <= buf.length - 8`. + */ + readBigUInt64LE(offset?: number): bigint; + /** + * Reads a signed, big-endian 64-bit integer from `buf` at the specified `offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed + * values. + * @since v12.0.0, v10.20.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy: `0 <= offset <= buf.length - 8`. + */ + readBigInt64BE(offset?: number): bigint; + /** + * Reads a signed, little-endian 64-bit integer from `buf` at the specified`offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed + * values. + * @since v12.0.0, v10.20.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy: `0 <= offset <= buf.length - 8`. + */ + readBigInt64LE(offset?: number): bigint; + /** + * Reads `byteLength` number of bytes from `buf` at the specified `offset`and interprets the result as an unsigned, little-endian integer supporting + * up to 48 bits of accuracy. + * + * This function is also available under the `readUintLE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]); + * + * console.log(buf.readUIntLE(0, 6).toString(16)); + * // Prints: ab9078563412 + * ``` + * @since v0.11.15 + * @param offset Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to read. Must satisfy `0 < byteLength <= 6`. + */ + readUIntLE(offset: number, byteLength: number): number; + /** + * Reads `byteLength` number of bytes from `buf` at the specified `offset`and interprets the result as an unsigned big-endian integer supporting + * up to 48 bits of accuracy. + * + * This function is also available under the `readUintBE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]); + * + * console.log(buf.readUIntBE(0, 6).toString(16)); + * // Prints: 1234567890ab + * console.log(buf.readUIntBE(1, 6).toString(16)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.11.15 + * @param offset Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to read. Must satisfy `0 < byteLength <= 6`. + */ + readUIntBE(offset: number, byteLength: number): number; + /** + * Reads `byteLength` number of bytes from `buf` at the specified `offset`and interprets the result as a little-endian, two's complement signed value + * supporting up to 48 bits of accuracy. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]); + * + * console.log(buf.readIntLE(0, 6).toString(16)); + * // Prints: -546f87a9cbee + * ``` + * @since v0.11.15 + * @param offset Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to read. Must satisfy `0 < byteLength <= 6`. + */ + readIntLE(offset: number, byteLength: number): number; + /** + * Reads `byteLength` number of bytes from `buf` at the specified `offset`and interprets the result as a big-endian, two's complement signed value + * supporting up to 48 bits of accuracy. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]); + * + * console.log(buf.readIntBE(0, 6).toString(16)); + * // Prints: 1234567890ab + * console.log(buf.readIntBE(1, 6).toString(16)); + * // Throws ERR_OUT_OF_RANGE. + * console.log(buf.readIntBE(1, 0).toString(16)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.11.15 + * @param offset Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to read. Must satisfy `0 < byteLength <= 6`. + */ + readIntBE(offset: number, byteLength: number): number; + /** + * Reads an unsigned 8-bit integer from `buf` at the specified `offset`. + * + * This function is also available under the `readUint8` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([1, -2]); + * + * console.log(buf.readUInt8(0)); + * // Prints: 1 + * console.log(buf.readUInt8(1)); + * // Prints: 254 + * console.log(buf.readUInt8(2)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 1`. + */ + readUInt8(offset?: number): number; + /** + * Reads an unsigned, little-endian 16-bit integer from `buf` at the specified`offset`. + * + * This function is also available under the `readUint16LE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x12, 0x34, 0x56]); + * + * console.log(buf.readUInt16LE(0).toString(16)); + * // Prints: 3412 + * console.log(buf.readUInt16LE(1).toString(16)); + * // Prints: 5634 + * console.log(buf.readUInt16LE(2).toString(16)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 2`. + */ + readUInt16LE(offset?: number): number; + /** + * Reads an unsigned, big-endian 16-bit integer from `buf` at the specified`offset`. + * + * This function is also available under the `readUint16BE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x12, 0x34, 0x56]); + * + * console.log(buf.readUInt16BE(0).toString(16)); + * // Prints: 1234 + * console.log(buf.readUInt16BE(1).toString(16)); + * // Prints: 3456 + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 2`. + */ + readUInt16BE(offset?: number): number; + /** + * Reads an unsigned, little-endian 32-bit integer from `buf` at the specified`offset`. + * + * This function is also available under the `readUint32LE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78]); + * + * console.log(buf.readUInt32LE(0).toString(16)); + * // Prints: 78563412 + * console.log(buf.readUInt32LE(1).toString(16)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`. + */ + readUInt32LE(offset?: number): number; + /** + * Reads an unsigned, big-endian 32-bit integer from `buf` at the specified`offset`. + * + * This function is also available under the `readUint32BE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78]); + * + * console.log(buf.readUInt32BE(0).toString(16)); + * // Prints: 12345678 + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`. + */ + readUInt32BE(offset?: number): number; + /** + * Reads a signed 8-bit integer from `buf` at the specified `offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed values. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([-1, 5]); + * + * console.log(buf.readInt8(0)); + * // Prints: -1 + * console.log(buf.readInt8(1)); + * // Prints: 5 + * console.log(buf.readInt8(2)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 1`. + */ + readInt8(offset?: number): number; + /** + * Reads a signed, little-endian 16-bit integer from `buf` at the specified`offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed values. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0, 5]); + * + * console.log(buf.readInt16LE(0)); + * // Prints: 1280 + * console.log(buf.readInt16LE(1)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 2`. + */ + readInt16LE(offset?: number): number; + /** + * Reads a signed, big-endian 16-bit integer from `buf` at the specified `offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed values. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0, 5]); + * + * console.log(buf.readInt16BE(0)); + * // Prints: 5 + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 2`. + */ + readInt16BE(offset?: number): number; + /** + * Reads a signed, little-endian 32-bit integer from `buf` at the specified`offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed values. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0, 0, 0, 5]); + * + * console.log(buf.readInt32LE(0)); + * // Prints: 83886080 + * console.log(buf.readInt32LE(1)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`. + */ + readInt32LE(offset?: number): number; + /** + * Reads a signed, big-endian 32-bit integer from `buf` at the specified `offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed values. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0, 0, 0, 5]); + * + * console.log(buf.readInt32BE(0)); + * // Prints: 5 + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`. + */ + readInt32BE(offset?: number): number; + /** + * Reads a 32-bit, little-endian float from `buf` at the specified `offset`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([1, 2, 3, 4]); + * + * console.log(buf.readFloatLE(0)); + * // Prints: 1.539989614439558e-36 + * console.log(buf.readFloatLE(1)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.11.15 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`. + */ + readFloatLE(offset?: number): number; + /** + * Reads a 32-bit, big-endian float from `buf` at the specified `offset`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([1, 2, 3, 4]); + * + * console.log(buf.readFloatBE(0)); + * // Prints: 2.387939260590663e-38 + * ``` + * @since v0.11.15 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`. + */ + readFloatBE(offset?: number): number; + /** + * Reads a 64-bit, little-endian double from `buf` at the specified `offset`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]); + * + * console.log(buf.readDoubleLE(0)); + * // Prints: 5.447603722011605e-270 + * console.log(buf.readDoubleLE(1)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.11.15 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 8`. + */ + readDoubleLE(offset?: number): number; + /** + * Reads a 64-bit, big-endian double from `buf` at the specified `offset`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]); + * + * console.log(buf.readDoubleBE(0)); + * // Prints: 8.20788039913184e-304 + * ``` + * @since v0.11.15 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 8`. + */ + readDoubleBE(offset?: number): number; + reverse(): this; + /** + * Interprets `buf` as an array of unsigned 16-bit integers and swaps the + * byte order _in-place_. Throws `ERR_INVALID_BUFFER_SIZE` if `buf.length` is not a multiple of 2. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf1 = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]); + * + * console.log(buf1); + * // Prints: <Buffer 01 02 03 04 05 06 07 08> + * + * buf1.swap16(); + * + * console.log(buf1); + * // Prints: <Buffer 02 01 04 03 06 05 08 07> + * + * const buf2 = Buffer.from([0x1, 0x2, 0x3]); + * + * buf2.swap16(); + * // Throws ERR_INVALID_BUFFER_SIZE. + * ``` + * + * One convenient use of `buf.swap16()` is to perform a fast in-place conversion + * between UTF-16 little-endian and UTF-16 big-endian: + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from('This is little-endian UTF-16', 'utf16le'); + * buf.swap16(); // Convert to big-endian UTF-16 text. + * ``` + * @since v5.10.0 + * @return A reference to `buf`. + */ + swap16(): Buffer; + /** + * Interprets `buf` as an array of unsigned 32-bit integers and swaps the + * byte order _in-place_. Throws `ERR_INVALID_BUFFER_SIZE` if `buf.length` is not a multiple of 4. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf1 = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]); + * + * console.log(buf1); + * // Prints: <Buffer 01 02 03 04 05 06 07 08> + * + * buf1.swap32(); + * + * console.log(buf1); + * // Prints: <Buffer 04 03 02 01 08 07 06 05> + * + * const buf2 = Buffer.from([0x1, 0x2, 0x3]); + * + * buf2.swap32(); + * // Throws ERR_INVALID_BUFFER_SIZE. + * ``` + * @since v5.10.0 + * @return A reference to `buf`. + */ + swap32(): Buffer; + /** + * Interprets `buf` as an array of 64-bit numbers and swaps byte order _in-place_. + * Throws `ERR_INVALID_BUFFER_SIZE` if `buf.length` is not a multiple of 8. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf1 = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]); + * + * console.log(buf1); + * // Prints: <Buffer 01 02 03 04 05 06 07 08> + * + * buf1.swap64(); + * + * console.log(buf1); + * // Prints: <Buffer 08 07 06 05 04 03 02 01> + * + * const buf2 = Buffer.from([0x1, 0x2, 0x3]); + * + * buf2.swap64(); + * // Throws ERR_INVALID_BUFFER_SIZE. + * ``` + * @since v6.3.0 + * @return A reference to `buf`. + */ + swap64(): Buffer; + /** + * Writes `value` to `buf` at the specified `offset`. `value` must be a + * valid unsigned 8-bit integer. Behavior is undefined when `value` is anything + * other than an unsigned 8-bit integer. + * + * This function is also available under the `writeUint8` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeUInt8(0x3, 0); + * buf.writeUInt8(0x4, 1); + * buf.writeUInt8(0x23, 2); + * buf.writeUInt8(0x42, 3); + * + * console.log(buf); + * // Prints: <Buffer 03 04 23 42> + * ``` + * @since v0.5.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 1`. + * @return `offset` plus the number of bytes written. + */ + writeUInt8(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. The `value`must be a valid unsigned 16-bit integer. Behavior is undefined when `value` is + * anything other than an unsigned 16-bit integer. + * + * This function is also available under the `writeUint16LE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeUInt16LE(0xdead, 0); + * buf.writeUInt16LE(0xbeef, 2); + * + * console.log(buf); + * // Prints: <Buffer ad de ef be> + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 2`. + * @return `offset` plus the number of bytes written. + */ + writeUInt16LE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. The `value`must be a valid unsigned 16-bit integer. Behavior is undefined when `value`is anything other than an + * unsigned 16-bit integer. + * + * This function is also available under the `writeUint16BE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeUInt16BE(0xdead, 0); + * buf.writeUInt16BE(0xbeef, 2); + * + * console.log(buf); + * // Prints: <Buffer de ad be ef> + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 2`. + * @return `offset` plus the number of bytes written. + */ + writeUInt16BE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. The `value`must be a valid unsigned 32-bit integer. Behavior is undefined when `value` is + * anything other than an unsigned 32-bit integer. + * + * This function is also available under the `writeUint32LE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeUInt32LE(0xfeedface, 0); + * + * console.log(buf); + * // Prints: <Buffer ce fa ed fe> + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`. + * @return `offset` plus the number of bytes written. + */ + writeUInt32LE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. The `value`must be a valid unsigned 32-bit integer. Behavior is undefined when `value`is anything other than an + * unsigned 32-bit integer. + * + * This function is also available under the `writeUint32BE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeUInt32BE(0xfeedface, 0); + * + * console.log(buf); + * // Prints: <Buffer fe ed fa ce> + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`. + * @return `offset` plus the number of bytes written. + */ + writeUInt32BE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset`. `value` must be a valid + * signed 8-bit integer. Behavior is undefined when `value` is anything other than + * a signed 8-bit integer. + * + * `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(2); + * + * buf.writeInt8(2, 0); + * buf.writeInt8(-2, 1); + * + * console.log(buf); + * // Prints: <Buffer 02 fe> + * ``` + * @since v0.5.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 1`. + * @return `offset` plus the number of bytes written. + */ + writeInt8(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. The `value`must be a valid signed 16-bit integer. Behavior is undefined when `value` is + * anything other than a signed 16-bit integer. + * + * The `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(2); + * + * buf.writeInt16LE(0x0304, 0); + * + * console.log(buf); + * // Prints: <Buffer 04 03> + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 2`. + * @return `offset` plus the number of bytes written. + */ + writeInt16LE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. The `value`must be a valid signed 16-bit integer. Behavior is undefined when `value` is + * anything other than a signed 16-bit integer. + * + * The `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(2); + * + * buf.writeInt16BE(0x0102, 0); + * + * console.log(buf); + * // Prints: <Buffer 01 02> + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 2`. + * @return `offset` plus the number of bytes written. + */ + writeInt16BE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. The `value`must be a valid signed 32-bit integer. Behavior is undefined when `value` is + * anything other than a signed 32-bit integer. + * + * The `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeInt32LE(0x05060708, 0); + * + * console.log(buf); + * // Prints: <Buffer 08 07 06 05> + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`. + * @return `offset` plus the number of bytes written. + */ + writeInt32LE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. The `value`must be a valid signed 32-bit integer. Behavior is undefined when `value` is + * anything other than a signed 32-bit integer. + * + * The `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeInt32BE(0x01020304, 0); + * + * console.log(buf); + * // Prints: <Buffer 01 02 03 04> + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`. + * @return `offset` plus the number of bytes written. + */ + writeInt32BE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. Behavior is + * undefined when `value` is anything other than a JavaScript number. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeFloatLE(0xcafebabe, 0); + * + * console.log(buf); + * // Prints: <Buffer bb fe 4a 4f> + * ``` + * @since v0.11.15 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`. + * @return `offset` plus the number of bytes written. + */ + writeFloatLE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. Behavior is + * undefined when `value` is anything other than a JavaScript number. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeFloatBE(0xcafebabe, 0); + * + * console.log(buf); + * // Prints: <Buffer 4f 4a fe bb> + * ``` + * @since v0.11.15 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`. + * @return `offset` plus the number of bytes written. + */ + writeFloatBE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. The `value`must be a JavaScript number. Behavior is undefined when `value` is anything + * other than a JavaScript number. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(8); + * + * buf.writeDoubleLE(123.456, 0); + * + * console.log(buf); + * // Prints: <Buffer 77 be 9f 1a 2f dd 5e 40> + * ``` + * @since v0.11.15 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 8`. + * @return `offset` plus the number of bytes written. + */ + writeDoubleLE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. The `value`must be a JavaScript number. Behavior is undefined when `value` is anything + * other than a JavaScript number. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(8); + * + * buf.writeDoubleBE(123.456, 0); + * + * console.log(buf); + * // Prints: <Buffer 40 5e dd 2f 1a 9f be 77> + * ``` + * @since v0.11.15 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 8`. + * @return `offset` plus the number of bytes written. + */ + writeDoubleBE(value: number, offset?: number): number; + /** + * Fills `buf` with the specified `value`. If the `offset` and `end` are not given, + * the entire `buf` will be filled: + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Fill a `Buffer` with the ASCII character 'h'. + * + * const b = Buffer.allocUnsafe(50).fill('h'); + * + * console.log(b.toString()); + * // Prints: hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh + * ``` + * + * `value` is coerced to a `uint32` value if it is not a string, `Buffer`, or + * integer. If the resulting integer is greater than `255` (decimal), `buf` will be + * filled with `value & 255`. + * + * If the final write of a `fill()` operation falls on a multi-byte character, + * then only the bytes of that character that fit into `buf` are written: + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Fill a `Buffer` with character that takes up two bytes in UTF-8. + * + * console.log(Buffer.allocUnsafe(5).fill('\u0222')); + * // Prints: <Buffer c8 a2 c8 a2 c8> + * ``` + * + * If `value` contains invalid characters, it is truncated; if no valid + * fill data remains, an exception is thrown: + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(5); + * + * console.log(buf.fill('a')); + * // Prints: <Buffer 61 61 61 61 61> + * console.log(buf.fill('aazz', 'hex')); + * // Prints: <Buffer aa aa aa aa aa> + * console.log(buf.fill('zz', 'hex')); + * // Throws an exception. + * ``` + * @since v0.5.0 + * @param value The value with which to fill `buf`. + * @param [offset=0] Number of bytes to skip before starting to fill `buf`. + * @param [end=buf.length] Where to stop filling `buf` (not inclusive). + * @param [encoding='utf8'] The encoding for `value` if `value` is a string. + * @return A reference to `buf`. + */ + fill( + value: string | Uint8Array | number, + offset?: number, + end?: number, + encoding?: Encoding, + ): this; + /** + * If `value` is: + * + * * a string, `value` is interpreted according to the character encoding in`encoding`. + * * a `Buffer` or [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), `value` will be used in its entirety. + * To compare a partial `Buffer`, use `buf.slice()`. + * * a number, `value` will be interpreted as an unsigned 8-bit integer + * value between `0` and `255`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from('this is a buffer'); + * + * console.log(buf.indexOf('this')); + * // Prints: 0 + * console.log(buf.indexOf('is')); + * // Prints: 2 + * console.log(buf.indexOf(Buffer.from('a buffer'))); + * // Prints: 8 + * console.log(buf.indexOf(97)); + * // Prints: 8 (97 is the decimal ASCII value for 'a') + * console.log(buf.indexOf(Buffer.from('a buffer example'))); + * // Prints: -1 + * console.log(buf.indexOf(Buffer.from('a buffer example').slice(0, 8))); + * // Prints: 8 + * + * const utf16Buffer = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'utf16le'); + * + * console.log(utf16Buffer.indexOf('\u03a3', 0, 'utf16le')); + * // Prints: 4 + * console.log(utf16Buffer.indexOf('\u03a3', -4, 'utf16le')); + * // Prints: 6 + * ``` + * + * If `value` is not a string, number, or `Buffer`, this method will throw a`TypeError`. If `value` is a number, it will be coerced to a valid byte value, + * an integer between 0 and 255. + * + * If `byteOffset` is not a number, it will be coerced to a number. If the result + * of coercion is `NaN` or `0`, then the entire buffer will be searched. This + * behavior matches [`String.prototype.indexOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf). + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const b = Buffer.from('abcdef'); + * + * // Passing a value that's a number, but not a valid byte. + * // Prints: 2, equivalent to searching for 99 or 'c'. + * console.log(b.indexOf(99.9)); + * console.log(b.indexOf(256 + 99)); + * + * // Passing a byteOffset that coerces to NaN or 0. + * // Prints: 1, searching the whole buffer. + * console.log(b.indexOf('b', undefined)); + * console.log(b.indexOf('b', {})); + * console.log(b.indexOf('b', null)); + * console.log(b.indexOf('b', [])); + * ``` + * + * If `value` is an empty string or empty `Buffer` and `byteOffset` is less + * than `buf.length`, `byteOffset` will be returned. If `value` is empty and`byteOffset` is at least `buf.length`, `buf.length` will be returned. + * @since v1.5.0 + * @param value What to search for. + * @param [byteOffset=0] Where to begin searching in `buf`. If negative, then offset is calculated from the end of `buf`. + * @param [encoding='utf8'] If `value` is a string, this is the encoding used to determine the binary representation of the string that will be searched for in `buf`. + * @return The index of the first occurrence of `value` in `buf`, or `-1` if `buf` does not contain `value`. + */ + indexOf( + value: string | number | Uint8Array, + byteOffset?: number, + encoding?: Encoding, + ): number; + /** + * Identical to `buf.indexOf()`, except the last occurrence of `value` is found + * rather than the first occurrence. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from('this buffer is a buffer'); + * + * console.log(buf.lastIndexOf('this')); + * // Prints: 0 + * console.log(buf.lastIndexOf('buffer')); + * // Prints: 17 + * console.log(buf.lastIndexOf(Buffer.from('buffer'))); + * // Prints: 17 + * console.log(buf.lastIndexOf(97)); + * // Prints: 15 (97 is the decimal ASCII value for 'a') + * console.log(buf.lastIndexOf(Buffer.from('yolo'))); + * // Prints: -1 + * console.log(buf.lastIndexOf('buffer', 5)); + * // Prints: 5 + * console.log(buf.lastIndexOf('buffer', 4)); + * // Prints: -1 + * + * const utf16Buffer = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'utf16le'); + * + * console.log(utf16Buffer.lastIndexOf('\u03a3', undefined, 'utf16le')); + * // Prints: 6 + * console.log(utf16Buffer.lastIndexOf('\u03a3', -5, 'utf16le')); + * // Prints: 4 + * ``` + * + * If `value` is not a string, number, or `Buffer`, this method will throw a`TypeError`. If `value` is a number, it will be coerced to a valid byte value, + * an integer between 0 and 255. + * + * If `byteOffset` is not a number, it will be coerced to a number. Any arguments + * that coerce to `NaN`, like `{}` or `undefined`, will search the whole buffer. + * This behavior matches [`String.prototype.lastIndexOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf). + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const b = Buffer.from('abcdef'); + * + * // Passing a value that's a number, but not a valid byte. + * // Prints: 2, equivalent to searching for 99 or 'c'. + * console.log(b.lastIndexOf(99.9)); + * console.log(b.lastIndexOf(256 + 99)); + * + * // Passing a byteOffset that coerces to NaN. + * // Prints: 1, searching the whole buffer. + * console.log(b.lastIndexOf('b', undefined)); + * console.log(b.lastIndexOf('b', {})); + * + * // Passing a byteOffset that coerces to 0. + * // Prints: -1, equivalent to passing 0. + * console.log(b.lastIndexOf('b', null)); + * console.log(b.lastIndexOf('b', [])); + * ``` + * + * If `value` is an empty string or empty `Buffer`, `byteOffset` will be returned. + * @since v6.0.0 + * @param value What to search for. + * @param [byteOffset=buf.length - 1] Where to begin searching in `buf`. If negative, then offset is calculated from the end of `buf`. + * @param [encoding='utf8'] If `value` is a string, this is the encoding used to determine the binary representation of the string that will be searched for in `buf`. + * @return The index of the last occurrence of `value` in `buf`, or `-1` if `buf` does not contain `value`. + */ + lastIndexOf( + value: string | number | Uint8Array, + byteOffset?: number, + encoding?: Encoding, + ): number; + /** + * Creates and returns an [iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) of `[index, byte]` pairs from the contents + * of `buf`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Log the entire contents of a `Buffer`. + * + * const buf = Buffer.from('buffer'); + * + * for (const pair of buf.entries()) { + * console.log(pair); + * } + * // Prints: + * // [0, 98] + * // [1, 117] + * // [2, 102] + * // [3, 102] + * // [4, 101] + * // [5, 114] + * ``` + * @since v1.1.0 + */ + entries(): IterableIterator<[number, number]>; + /** + * Equivalent to `buf.indexOf() !== -1`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from('this is a buffer'); + * + * console.log(buf.includes('this')); + * // Prints: true + * console.log(buf.includes('is')); + * // Prints: true + * console.log(buf.includes(Buffer.from('a buffer'))); + * // Prints: true + * console.log(buf.includes(97)); + * // Prints: true (97 is the decimal ASCII value for 'a') + * console.log(buf.includes(Buffer.from('a buffer example'))); + * // Prints: false + * console.log(buf.includes(Buffer.from('a buffer example').slice(0, 8))); + * // Prints: true + * console.log(buf.includes('this', 4)); + * // Prints: false + * ``` + * @since v5.3.0 + * @param value What to search for. + * @param [byteOffset=0] Where to begin searching in `buf`. If negative, then offset is calculated from the end of `buf`. + * @param [encoding='utf8'] If `value` is a string, this is its encoding. + * @return `true` if `value` was found in `buf`, `false` otherwise. + */ + includes( + value: string | number | Buffer, + byteOffset?: number, + encoding?: Encoding, + ): boolean; + /** + * Creates and returns an [iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) of `buf` keys (indices). + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from('buffer'); + * + * for (const key of buf.keys()) { + * console.log(key); + * } + * // Prints: + * // 0 + * // 1 + * // 2 + * // 3 + * // 4 + * // 5 + * ``` + * @since v1.1.0 + */ + keys(): IterableIterator<number>; + /** + * Creates and returns an [iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) for `buf` values (bytes). This function is + * called automatically when a `Buffer` is used in a `for..of` statement. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from('buffer'); + * + * for (const value of buf.values()) { + * console.log(value); + * } + * // Prints: + * // 98 + * // 117 + * // 102 + * // 102 + * // 101 + * // 114 + * + * for (const value of buf) { + * console.log(value); + * } + * // Prints: + * // 98 + * // 117 + * // 102 + * // 102 + * // 101 + * // 114 + * ``` + * @since v1.1.0 + */ + values(): IterableIterator<number>; +} + +export const SlowBuffer: { + /** @deprecated since v6.0.0, use `Buffer.allocUnsafeSlow()` */ + new (size: number): Buffer; + prototype: Buffer; +}; + +export const atob: typeof globalThis.atob; +export const Blob: Blob; +export const btoa: typeof globalThis.btoa; +export const constants: { + MAX_LENGTH: number; + MAX_STRING_LENGTH: number; +}; +export const kMaxLength: number; +export const kStringMaxLength: number; + +declare const exports: { + atob: typeof atob; + Blob: Blob; + btoa: typeof btoa; + Buffer: Buffer; + constants: typeof constants; + kMaxLength: typeof kMaxLength; + kStringMaxLength: typeof kStringMaxLength; + SlowBuffer: typeof SlowBuffer; +}; + +export default exports; diff --git a/ext/node/polyfills/internal/buffer.mjs b/ext/node/polyfills/internal/buffer.mjs new file mode 100644 index 000000000..506f90507 --- /dev/null +++ b/ext/node/polyfills/internal/buffer.mjs @@ -0,0 +1,2607 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Copyright Feross Aboukhadijeh, and other contributors. All rights reserved. MIT license. + +import { TextDecoder, TextEncoder } from "internal:deno_web/08_text_encoding.js"; +import { codes } from "internal:deno_node/polyfills/internal/error_codes.ts"; +import { encodings } from "internal:deno_node/polyfills/internal_binding/string_decoder.ts"; +import { indexOfBuffer, indexOfNumber } from "internal:deno_node/polyfills/internal_binding/buffer.ts"; +import { + asciiToBytes, + base64ToBytes, + base64UrlToBytes, + bytesToAscii, + bytesToUtf16le, + hexToBytes, + utf16leToBytes, +} from "internal:deno_node/polyfills/internal_binding/_utils.ts"; +import { isAnyArrayBuffer, isArrayBufferView } from "internal:deno_node/polyfills/internal/util/types.ts"; +import { normalizeEncoding } from "internal:deno_node/polyfills/internal/util.mjs"; +import { validateBuffer } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { isUint8Array } from "internal:deno_node/polyfills/internal/util/types.ts"; +import { forgivingBase64Encode, forgivingBase64UrlEncode } from "internal:deno_web/00_infra.js"; + +const utf8Encoder = new TextEncoder(); + +// Temporary buffers to convert numbers. +const float32Array = new Float32Array(1); +const uInt8Float32Array = new Uint8Array(float32Array.buffer); +const float64Array = new Float64Array(1); +const uInt8Float64Array = new Uint8Array(float64Array.buffer); + +// Check endianness. +float32Array[0] = -1; // 0xBF800000 +// Either it is [0, 0, 128, 191] or [191, 128, 0, 0]. It is not possible to +// check this with `os.endianness()` because that is determined at compile time. +export const bigEndian = uInt8Float32Array[3] === 0; + +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 INSPECT_MAX_BYTES = 50; + +export const constants = { + MAX_LENGTH: kMaxLength, + MAX_STRING_LENGTH: kStringMaxLength, +}; + +Object.defineProperty(Buffer.prototype, "parent", { + enumerable: true, + get: function () { + if (!Buffer.isBuffer(this)) { + return void 0; + } + return this.buffer; + }, +}); + +Object.defineProperty(Buffer.prototype, "offset", { + enumerable: true, + get: function () { + if (!Buffer.isBuffer(this)) { + return void 0; + } + return this.byteOffset; + }, +}); + +function createBuffer(length) { + if (length > kMaxLength) { + throw new RangeError( + 'The value "' + length + '" is invalid for option "size"', + ); + } + const buf = new Uint8Array(length); + Object.setPrototypeOf(buf, Buffer.prototype); + return buf; +} + +export function Buffer(arg, encodingOrOffset, length) { + if (typeof arg === "number") { + if (typeof encodingOrOffset === "string") { + throw new codes.ERR_INVALID_ARG_TYPE( + "string", + "string", + arg, + ); + } + return _allocUnsafe(arg); + } + return _from(arg, encodingOrOffset, length); +} + +Buffer.poolSize = 8192; + +function _from(value, encodingOrOffset, length) { + if (typeof value === "string") { + return fromString(value, encodingOrOffset); + } + + if (typeof value === "object" && value !== null) { + if (isAnyArrayBuffer(value)) { + return fromArrayBuffer(value, encodingOrOffset, length); + } + + const valueOf = value.valueOf && value.valueOf(); + if ( + valueOf != null && + valueOf !== value && + (typeof valueOf === "string" || typeof valueOf === "object") + ) { + return _from(valueOf, encodingOrOffset, length); + } + + const b = fromObject(value); + if (b) { + return b; + } + + if (typeof value[Symbol.toPrimitive] === "function") { + const primitive = value[Symbol.toPrimitive]("string"); + if (typeof primitive === "string") { + return fromString(primitive, encodingOrOffset); + } + } + } + + throw new codes.ERR_INVALID_ARG_TYPE( + "first argument", + ["string", "Buffer", "ArrayBuffer", "Array", "Array-like Object"], + value, + ); +} + +Buffer.from = function from(value, encodingOrOffset, length) { + return _from(value, encodingOrOffset, length); +}; + +Object.setPrototypeOf(Buffer.prototype, Uint8Array.prototype); + +Object.setPrototypeOf(Buffer, Uint8Array); + +function assertSize(size) { + validateNumber(size, "size"); + if (!(size >= 0 && size <= kMaxLength)) { + throw new codes.ERR_INVALID_ARG_VALUE.RangeError("size", size); + } +} + +function _alloc(size, fill, encoding) { + assertSize(size); + + const buffer = createBuffer(size); + if (fill !== undefined) { + if (encoding !== undefined && typeof encoding !== "string") { + throw new codes.ERR_INVALID_ARG_TYPE( + "encoding", + "string", + encoding, + ); + } + return buffer.fill(fill, encoding); + } + return buffer; +} + +Buffer.alloc = function alloc(size, fill, encoding) { + return _alloc(size, fill, encoding); +}; + +function _allocUnsafe(size) { + assertSize(size); + return createBuffer(size < 0 ? 0 : checked(size) | 0); +} + +Buffer.allocUnsafe = function allocUnsafe(size) { + return _allocUnsafe(size); +}; + +Buffer.allocUnsafeSlow = function allocUnsafeSlow(size) { + return _allocUnsafe(size); +}; + +function fromString(string, encoding) { + if (typeof encoding !== "string" || encoding === "") { + encoding = "utf8"; + } + if (!Buffer.isEncoding(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) { + buf = buf.slice(0, actual); + } + return buf; +} + +function fromArrayLike(array) { + const length = array.length < 0 ? 0 : checked(array.length) | 0; + const buf = createBuffer(length); + for (let i = 0; i < length; i += 1) { + buf[i] = array[i] & 255; + } + return buf; +} + +function fromObject(obj) { + if (obj.length !== undefined || isAnyArrayBuffer(obj.buffer)) { + if (typeof obj.length !== "number") { + return createBuffer(0); + } + return fromArrayLike(obj); + } + + if (obj.type === "Buffer" && Array.isArray(obj.data)) { + return fromArrayLike(obj.data); + } +} + +function checked(length) { + if (length >= kMaxLength) { + throw new RangeError( + "Attempt to allocate Buffer larger than maximum size: 0x" + + kMaxLength.toString(16) + " bytes", + ); + } + return length | 0; +} + +export function SlowBuffer(length) { + assertSize(length); + return Buffer.alloc(+length); +} + +Object.setPrototypeOf(SlowBuffer.prototype, Uint8Array.prototype); + +Object.setPrototypeOf(SlowBuffer, Uint8Array); + +Buffer.isBuffer = function isBuffer(b) { + return b != null && b._isBuffer === true && b !== Buffer.prototype; +}; + +Buffer.compare = function compare(a, b) { + if (isInstance(a, Uint8Array)) { + a = Buffer.from(a, a.offset, a.byteLength); + } + if (isInstance(b, Uint8Array)) { + b = Buffer.from(b, b.offset, b.byteLength); + } + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError( + 'The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array', + ); + } + if (a === b) { + return 0; + } + let x = a.length; + let y = b.length; + for (let i = 0, len = Math.min(x, y); i < len; ++i) { + if (a[i] !== b[i]) { + x = a[i]; + y = b[i]; + break; + } + } + if (x < y) { + return -1; + } + if (y < x) { + return 1; + } + return 0; +}; + +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)) { + throw new codes.ERR_INVALID_ARG_TYPE("list", "Array", list); + } + + if (list.length === 0) { + return Buffer.alloc(0); + } + + if (length === undefined) { + length = 0; + for (let i = 0; i < list.length; i++) { + if (list[i].length) { + length += list[i].length; + } + } + } else { + validateOffset(length, "length"); + } + + const buffer = Buffer.allocUnsafe(length); + let pos = 0; + for (let i = 0; i < list.length; i++) { + const buf = list[i]; + if (!isUint8Array(buf)) { + // TODO(BridgeAR): This should not be of type ERR_INVALID_ARG_TYPE. + // Instead, find the proper error code for this. + throw new codes.ERR_INVALID_ARG_TYPE( + `list[${i}]`, + ["Buffer", "Uint8Array"], + list[i], + ); + } + pos += _copyActual(buf, buffer, pos, 0, buf.length); + } + + // Note: `length` is always equal to `buffer.length` at this point + if (pos < 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); + } + + return buffer; +}; + +function byteLength(string, encoding) { + if (typeof string !== "string") { + if (isArrayBufferView(string) || isAnyArrayBuffer(string)) { + return string.byteLength; + } + + throw new codes.ERR_INVALID_ARG_TYPE( + "string", + ["string", "Buffer", "ArrayBuffer"], + string, + ); + } + + const len = string.length; + const mustMatch = arguments.length > 2 && arguments[2] === true; + if (!mustMatch && len === 0) { + return 0; + } + + if (!encoding) { + return (mustMatch ? -1 : byteLengthUtf8(string)); + } + + const ops = getEncodingOps(encoding); + if (ops === undefined) { + return (mustMatch ? -1 : byteLengthUtf8(string)); + } + return ops.byteLength(string); +} + +Buffer.byteLength = byteLength; + +Buffer.prototype._isBuffer = true; + +function swap(b, n, m) { + const i = b[n]; + b[n] = b[m]; + b[m] = i; +} + +Buffer.prototype.swap16 = function swap16() { + const len = this.length; + if (len % 2 !== 0) { + throw new RangeError("Buffer size must be a multiple of 16-bits"); + } + for (let i = 0; i < len; i += 2) { + swap(this, i, i + 1); + } + return this; +}; + +Buffer.prototype.swap32 = function swap32() { + const len = this.length; + if (len % 4 !== 0) { + throw new RangeError("Buffer size must be a multiple of 32-bits"); + } + for (let i = 0; i < len; i += 4) { + swap(this, i, i + 3); + swap(this, i + 1, i + 2); + } + return this; +}; + +Buffer.prototype.swap64 = function swap64() { + const len = this.length; + if (len % 8 !== 0) { + throw new RangeError("Buffer size must be a multiple of 64-bits"); + } + for (let i = 0; i < len; i += 8) { + swap(this, i, i + 7); + swap(this, i + 1, i + 6); + swap(this, i + 2, i + 5); + swap(this, i + 3, i + 4); + } + return this; +}; + +Buffer.prototype.toString = function toString(encoding, start, end) { + if (arguments.length === 0) { + return this.utf8Slice(0, this.length); + } + + const len = this.length; + + if (start <= 0) { + start = 0; + } else if (start >= len) { + return ""; + } else { + start |= 0; + } + + if (end === undefined || end > len) { + end = len; + } else { + end |= 0; + } + + if (end <= start) { + return ""; + } + + if (encoding === undefined) { + return this.utf8Slice(start, end); + } + + const ops = getEncodingOps(encoding); + if (ops === undefined) { + throw new codes.ERR_UNKNOWN_ENCODING(encoding); + } + + return ops.slice(this, start, end); +}; + +Buffer.prototype.toLocaleString = Buffer.prototype.toString; + +Buffer.prototype.equals = function equals(b) { + if (!isUint8Array(b)) { + throw new codes.ERR_INVALID_ARG_TYPE( + "otherBuffer", + ["Buffer", "Uint8Array"], + b, + ); + } + if (this === b) { + return true; + } + return Buffer.compare(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 + ">"; +}; + +if (customInspectSymbol) { + Buffer.prototype[customInspectSymbol] = Buffer.prototype.inspect; +} + +Buffer.prototype.compare = function compare( + target, + start, + end, + thisStart, + thisEnd, +) { + if (isInstance(target, Uint8Array)) { + target = Buffer.from(target, target.offset, target.byteLength); + } + if (!Buffer.isBuffer(target)) { + throw new codes.ERR_INVALID_ARG_TYPE( + "target", + ["Buffer", "Uint8Array"], + target, + ); + } + + if (start === undefined) { + start = 0; + } else { + validateOffset(start, "targetStart", 0, kMaxLength); + } + + if (end === undefined) { + end = target.length; + } else { + validateOffset(end, "targetEnd", 0, target.length); + } + + if (thisStart === undefined) { + thisStart = 0; + } else { + validateOffset(start, "sourceStart", 0, kMaxLength); + } + + if (thisEnd === undefined) { + thisEnd = this.length; + } else { + validateOffset(end, "sourceEnd", 0, this.length); + } + + if ( + start < 0 || end > target.length || thisStart < 0 || + thisEnd > this.length + ) { + throw new codes.ERR_OUT_OF_RANGE("out of range index", "range"); + } + + if (thisStart >= thisEnd && start >= end) { + return 0; + } + if (thisStart >= thisEnd) { + return -1; + } + if (start >= end) { + return 1; + } + start >>>= 0; + end >>>= 0; + thisStart >>>= 0; + thisEnd >>>= 0; + if (this === target) { + return 0; + } + let x = thisEnd - thisStart; + let y = end - start; + const len = Math.min(x, y); + const thisCopy = this.slice(thisStart, thisEnd); + const targetCopy = target.slice(start, end); + for (let i = 0; i < len; ++i) { + if (thisCopy[i] !== targetCopy[i]) { + x = thisCopy[i]; + y = targetCopy[i]; + break; + } + } + if (x < y) { + return -1; + } + if (y < x) { + return 1; + } + return 0; +}; + +function bidirectionalIndexOf(buffer, val, byteOffset, encoding, dir) { + validateBuffer(buffer); + + if (typeof byteOffset === "string") { + encoding = byteOffset; + byteOffset = undefined; + } else if (byteOffset > 0x7fffffff) { + byteOffset = 0x7fffffff; + } else if (byteOffset < -0x80000000) { + byteOffset = -0x80000000; + } + byteOffset = +byteOffset; + if (Number.isNaN(byteOffset)) { + byteOffset = dir ? 0 : (buffer.length || buffer.byteLength); + } + dir = !!dir; + + if (typeof val === "number") { + return indexOfNumber(buffer, val >>> 0, byteOffset, dir); + } + + let ops; + if (encoding === undefined) { + ops = encodingOps.utf8; + } else { + ops = getEncodingOps(encoding); + } + + if (typeof val === "string") { + if (ops === undefined) { + throw new codes.ERR_UNKNOWN_ENCODING(encoding); + } + return ops.indexOf(buffer, val, byteOffset, dir); + } + + if (isUint8Array(val)) { + const encodingVal = ops === undefined ? encodingsMap.utf8 : ops.encodingVal; + return indexOfBuffer(buffer, val, byteOffset, encodingVal, dir); + } + + throw new codes.ERR_INVALID_ARG_TYPE( + "value", + ["number", "string", "Buffer", "Uint8Array"], + val, + ); +} + +Buffer.prototype.includes = function includes(val, byteOffset, encoding) { + return this.indexOf(val, byteOffset, encoding) !== -1; +}; + +Buffer.prototype.indexOf = function indexOf(val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, true); +}; + +Buffer.prototype.lastIndexOf = function lastIndexOf( + val, + byteOffset, + encoding, +) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, false); +}; + +Buffer.prototype.asciiSlice = function asciiSlice(offset, length) { + if (offset === 0 && length === this.length) { + return bytesToAscii(this); + } else { + return bytesToAscii(this.slice(offset, length)); + } +}; + +Buffer.prototype.asciiWrite = function asciiWrite(string, offset, length) { + return blitBuffer(asciiToBytes(string), this, offset, length); +}; + +Buffer.prototype.base64Slice = function base64Slice( + offset, + length, +) { + if (offset === 0 && length === this.length) { + return forgivingBase64Encode(this); + } else { + return forgivingBase64Encode(this.slice(offset, length)); + } +}; + +Buffer.prototype.base64Write = function base64Write( + string, + offset, + length, +) { + return blitBuffer(base64ToBytes(string), this, offset, length); +}; + +Buffer.prototype.base64urlSlice = function base64urlSlice( + offset, + length, +) { + if (offset === 0 && length === this.length) { + return forgivingBase64UrlEncode(this); + } else { + return forgivingBase64UrlEncode(this.slice(offset, length)); + } +}; + +Buffer.prototype.base64urlWrite = function base64urlWrite( + string, + offset, + length, +) { + return blitBuffer(base64UrlToBytes(string), this, offset, length); +}; + +Buffer.prototype.hexWrite = function hexWrite(string, offset, length) { + return blitBuffer( + hexToBytes(string, this.length - offset), + this, + offset, + length, + ); +}; + +Buffer.prototype.hexSlice = function hexSlice(string, offset, length) { + return _hexSlice(this, string, offset, length); +}; + +Buffer.prototype.latin1Slice = function latin1Slice( + string, + offset, + length, +) { + return _latin1Slice(this, string, offset, length); +}; + +Buffer.prototype.latin1Write = function latin1Write( + string, + offset, + length, +) { + return blitBuffer(asciiToBytes(string), this, offset, length); +}; + +Buffer.prototype.ucs2Slice = function ucs2Slice(offset, length) { + if (offset === 0 && length === this.length) { + return bytesToUtf16le(this); + } else { + return bytesToUtf16le(this.slice(offset, length)); + } +}; + +Buffer.prototype.ucs2Write = function ucs2Write(string, offset, length) { + return blitBuffer( + utf16leToBytes(string, this.length - offset), + this, + offset, + length, + ); +}; + +Buffer.prototype.utf8Slice = function utf8Slice(string, offset, length) { + return _utf8Slice(this, string, offset, length); +}; + +Buffer.prototype.utf8Write = function utf8Write(string, offset, length) { + return blitBuffer( + utf8ToBytes(string, this.length - offset), + this, + offset, + length, + ); +}; + +Buffer.prototype.write = function write(string, offset, length, encoding) { + // Buffer#write(string); + if (offset === undefined) { + return this.utf8Write(string, 0, this.length); + } + // Buffer#write(string, encoding) + if (length === undefined && typeof offset === "string") { + encoding = offset; + length = this.length; + offset = 0; + + // Buffer#write(string, offset[, length][, encoding]) + } else { + validateOffset(offset, "offset", 0, this.length); + + const remaining = this.length - offset; + + if (length === undefined) { + length = remaining; + } else if (typeof length === "string") { + encoding = length; + length = remaining; + } else { + validateOffset(length, "length", 0, this.length); + if (length > remaining) { + length = remaining; + } + } + } + + if (!encoding) { + return this.utf8Write(string, offset, length); + } + + const ops = getEncodingOps(encoding); + if (ops === undefined) { + throw new codes.ERR_UNKNOWN_ENCODING(encoding); + } + return ops.write(this, string, offset, length); +}; + +Buffer.prototype.toJSON = function toJSON() { + return { + type: "Buffer", + data: Array.prototype.slice.call(this._arr || this, 0), + }; +}; +function fromArrayBuffer(obj, byteOffset, length) { + // Convert byteOffset to integer + if (byteOffset === undefined) { + byteOffset = 0; + } else { + byteOffset = +byteOffset; + if (Number.isNaN(byteOffset)) { + byteOffset = 0; + } + } + + const maxLength = obj.byteLength - byteOffset; + + if (maxLength < 0) { + throw new codes.ERR_BUFFER_OUT_OF_BOUNDS("offset"); + } + + if (length === undefined) { + length = maxLength; + } else { + // Convert length to non-negative integer. + length = +length; + if (length > 0) { + if (length > maxLength) { + throw new codes.ERR_BUFFER_OUT_OF_BOUNDS("length"); + } + } else { + length = 0; + } + } + + const buffer = new Uint8Array(obj, byteOffset, length); + Object.setPrototypeOf(buffer, Buffer.prototype); + return buffer; +} + +function _base64Slice(buf, start, end) { + if (start === 0 && end === buf.length) { + return forgivingBase64Encode(buf); + } else { + return forgivingBase64Encode(buf.slice(start, end)); + } +} + +const decoder = new TextDecoder(); + +function _utf8Slice(buf, start, end) { + return decoder.decode(buf.slice(start, end)); +} + +function _latin1Slice(buf, start, end) { + let ret = ""; + end = Math.min(buf.length, end); + for (let i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i]); + } + return ret; +} + +function _hexSlice(buf, start, end) { + const len = buf.length; + if (!start || start < 0) { + start = 0; + } + if (!end || end < 0 || end > len) { + end = len; + } + let out = ""; + for (let i = start; i < end; ++i) { + out += hexSliceLookupTable[buf[i]]; + } + return out; +} + +Buffer.prototype.slice = function slice(start, end) { + const len = this.length; + start = ~~start; + end = end === void 0 ? len : ~~end; + if (start < 0) { + start += len; + if (start < 0) { + start = 0; + } + } else if (start > len) { + start = len; + } + if (end < 0) { + end += len; + if (end < 0) { + end = 0; + } + } else if (end > len) { + end = len; + } + if (end < start) { + end = start; + } + const newBuf = this.subarray(start, end); + Object.setPrototypeOf(newBuf, Buffer.prototype); + return newBuf; +}; + +Buffer.prototype.readUintLE = Buffer.prototype.readUIntLE = function readUIntLE( + offset, + byteLength, +) { + if (offset === undefined) { + throw new codes.ERR_INVALID_ARG_TYPE("offset", "number", offset); + } + if (byteLength === 6) { + return readUInt48LE(this, offset); + } + if (byteLength === 5) { + return readUInt40LE(this, offset); + } + if (byteLength === 3) { + return readUInt24LE(this, offset); + } + if (byteLength === 4) { + return this.readUInt32LE(offset); + } + if (byteLength === 2) { + return this.readUInt16LE(offset); + } + if (byteLength === 1) { + return this.readUInt8(offset); + } + + boundsError(byteLength, 6, "byteLength"); +}; + +Buffer.prototype.readUintBE = Buffer.prototype.readUIntBE = function readUIntBE( + offset, + byteLength, +) { + if (offset === undefined) { + throw new codes.ERR_INVALID_ARG_TYPE("offset", "number", offset); + } + if (byteLength === 6) { + return readUInt48BE(this, offset); + } + if (byteLength === 5) { + return readUInt40BE(this, offset); + } + if (byteLength === 3) { + return readUInt24BE(this, offset); + } + if (byteLength === 4) { + return this.readUInt32BE(offset); + } + if (byteLength === 2) { + return this.readUInt16BE(offset); + } + if (byteLength === 1) { + return this.readUInt8(offset); + } + + boundsError(byteLength, 6, "byteLength"); +}; + +Buffer.prototype.readUint8 = Buffer.prototype.readUInt8 = function readUInt8( + offset = 0, +) { + validateNumber(offset, "offset"); + const val = this[offset]; + if (val === undefined) { + boundsError(offset, this.length - 1); + } + + return val; +}; + +Buffer.prototype.readUint16BE = Buffer.prototype.readUInt16BE = readUInt16BE; + +Buffer.prototype.readUint16LE = + Buffer.prototype.readUInt16LE = + function readUInt16LE(offset = 0) { + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 1]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 2); + } + + return first + last * 2 ** 8; + }; + +Buffer.prototype.readUint32LE = + Buffer.prototype.readUInt32LE = + function readUInt32LE(offset = 0) { + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 3]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 4); + } + + return first + + this[++offset] * 2 ** 8 + + this[++offset] * 2 ** 16 + + last * 2 ** 24; + }; + +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)); + }, + ); + +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); + }, + ); + +Buffer.prototype.readIntLE = function readIntLE( + offset, + byteLength, +) { + if (offset === undefined) { + throw new codes.ERR_INVALID_ARG_TYPE("offset", "number", offset); + } + if (byteLength === 6) { + return readInt48LE(this, offset); + } + if (byteLength === 5) { + return readInt40LE(this, offset); + } + if (byteLength === 3) { + return readInt24LE(this, offset); + } + if (byteLength === 4) { + return this.readInt32LE(offset); + } + if (byteLength === 2) { + return this.readInt16LE(offset); + } + if (byteLength === 1) { + return this.readInt8(offset); + } + + boundsError(byteLength, 6, "byteLength"); +}; + +Buffer.prototype.readIntBE = function readIntBE(offset, byteLength) { + if (offset === undefined) { + throw new codes.ERR_INVALID_ARG_TYPE("offset", "number", offset); + } + if (byteLength === 6) { + return readInt48BE(this, offset); + } + if (byteLength === 5) { + return readInt40BE(this, offset); + } + if (byteLength === 3) { + return readInt24BE(this, offset); + } + if (byteLength === 4) { + return this.readInt32BE(offset); + } + if (byteLength === 2) { + return this.readInt16BE(offset); + } + if (byteLength === 1) { + return this.readInt8(offset); + } + + boundsError(byteLength, 6, "byteLength"); +}; + +Buffer.prototype.readInt8 = function readInt8(offset = 0) { + validateNumber(offset, "offset"); + const val = this[offset]; + if (val === undefined) { + boundsError(offset, this.length - 1); + } + + return val | (val & 2 ** 7) * 0x1fffffe; +}; + +Buffer.prototype.readInt16LE = function readInt16LE(offset = 0) { + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 1]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 2); + } + + const val = first + last * 2 ** 8; + return val | (val & 2 ** 15) * 0x1fffe; +}; + +Buffer.prototype.readInt16BE = function readInt16BE(offset = 0) { + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 1]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 2); + } + + const val = first * 2 ** 8 + last; + return val | (val & 2 ** 15) * 0x1fffe; +}; + +Buffer.prototype.readInt32LE = function readInt32LE(offset = 0) { + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 3]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 4); + } + + return first + + this[++offset] * 2 ** 8 + + this[++offset] * 2 ** 16 + + (last << 24); // Overflow +}; + +Buffer.prototype.readInt32BE = function readInt32BE(offset = 0) { + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 3]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 4); + } + + return (first << 24) + // Overflow + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 8 + + 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.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.readFloatLE = function readFloatLE(offset) { + return bigEndian + ? readFloatBackwards(this, offset) + : readFloatForwards(this, offset); +}; + +Buffer.prototype.readFloatBE = function readFloatBE(offset) { + return bigEndian + ? readFloatForwards(this, offset) + : readFloatBackwards(this, offset); +}; + +Buffer.prototype.readDoubleLE = function readDoubleLE(offset) { + return bigEndian + ? readDoubleBackwards(this, offset) + : readDoubleForwards(this, offset); +}; + +Buffer.prototype.readDoubleBE = function readDoubleBE(offset) { + return bigEndian + ? readDoubleForwards(this, offset) + : readDoubleBackwards(this, offset); +}; + +Buffer.prototype.writeUintLE = + Buffer.prototype.writeUIntLE = + function writeUIntLE(value, offset, byteLength) { + if (byteLength === 6) { + return writeU_Int48LE(this, value, offset, 0, 0xffffffffffff); + } + if (byteLength === 5) { + return writeU_Int40LE(this, value, offset, 0, 0xffffffffff); + } + if (byteLength === 3) { + return writeU_Int24LE(this, value, offset, 0, 0xffffff); + } + if (byteLength === 4) { + return writeU_Int32LE(this, value, offset, 0, 0xffffffff); + } + if (byteLength === 2) { + return writeU_Int16LE(this, value, offset, 0, 0xffff); + } + if (byteLength === 1) { + return writeU_Int8(this, value, offset, 0, 0xff); + } + + boundsError(byteLength, 6, "byteLength"); + }; + +Buffer.prototype.writeUintBE = + Buffer.prototype.writeUIntBE = + function writeUIntBE(value, offset, byteLength) { + if (byteLength === 6) { + return writeU_Int48BE(this, value, offset, 0, 0xffffffffffff); + } + if (byteLength === 5) { + return writeU_Int40BE(this, value, offset, 0, 0xffffffffff); + } + if (byteLength === 3) { + return writeU_Int24BE(this, value, offset, 0, 0xffffff); + } + if (byteLength === 4) { + return writeU_Int32BE(this, value, offset, 0, 0xffffffff); + } + if (byteLength === 2) { + return writeU_Int16BE(this, value, offset, 0, 0xffff); + } + if (byteLength === 1) { + return writeU_Int8(this, value, offset, 0, 0xff); + } + + boundsError(byteLength, 6, "byteLength"); + }; + +Buffer.prototype.writeUint8 = Buffer.prototype.writeUInt8 = function writeUInt8( + value, + offset = 0, +) { + return writeU_Int8(this, value, offset, 0, 0xff); +}; + +Buffer.prototype.writeUint16LE = + Buffer.prototype.writeUInt16LE = + function writeUInt16LE(value, offset = 0) { + return writeU_Int16LE(this, value, offset, 0, 0xffff); + }; + +Buffer.prototype.writeUint16BE = + Buffer.prototype.writeUInt16BE = + function writeUInt16BE(value, offset = 0) { + return writeU_Int16BE(this, value, offset, 0, 0xffff); + }; + +Buffer.prototype.writeUint32LE = + Buffer.prototype.writeUInt32LE = + function writeUInt32LE(value, offset = 0) { + return _writeUInt32LE(this, value, offset, 0, 0xffffffff); + }; + +Buffer.prototype.writeUint32BE = + Buffer.prototype.writeUInt32BE = + function writeUInt32BE(value, offset = 0) { + return _writeUInt32BE(this, value, offset, 0, 0xffffffff); + }; + +function wrtBigUInt64LE(buf, value, offset, min, max) { + checkIntBI(value, min, max, buf, offset, 7); + let lo = Number(value & BigInt(4294967295)); + buf[offset++] = lo; + lo = lo >> 8; + buf[offset++] = lo; + lo = lo >> 8; + buf[offset++] = lo; + lo = lo >> 8; + buf[offset++] = lo; + let hi = Number(value >> BigInt(32) & BigInt(4294967295)); + buf[offset++] = hi; + hi = hi >> 8; + buf[offset++] = hi; + hi = hi >> 8; + buf[offset++] = hi; + hi = hi >> 8; + buf[offset++] = hi; + return offset; +} + +function wrtBigUInt64BE(buf, value, offset, min, max) { + checkIntBI(value, min, max, buf, offset, 7); + let lo = Number(value & BigInt(4294967295)); + buf[offset + 7] = lo; + lo = lo >> 8; + buf[offset + 6] = lo; + lo = lo >> 8; + buf[offset + 5] = lo; + lo = lo >> 8; + buf[offset + 4] = lo; + let hi = Number(value >> BigInt(32) & BigInt(4294967295)); + buf[offset + 3] = hi; + hi = hi >> 8; + buf[offset + 2] = hi; + hi = hi >> 8; + buf[offset + 1] = hi; + hi = hi >> 8; + buf[offset] = hi; + return offset + 8; +} + +Buffer.prototype.writeBigUint64LE = + Buffer.prototype.writeBigUInt64LE = + defineBigIntMethod( + function writeBigUInt64LE(value, offset = 0) { + return wrtBigUInt64LE( + this, + value, + offset, + BigInt(0), + BigInt("0xffffffffffffffff"), + ); + }, + ); + +Buffer.prototype.writeBigUint64BE = + Buffer.prototype.writeBigUInt64BE = + defineBigIntMethod( + function writeBigUInt64BE(value, offset = 0) { + return wrtBigUInt64BE( + this, + value, + offset, + BigInt(0), + BigInt("0xffffffffffffffff"), + ); + }, + ); + +Buffer.prototype.writeIntLE = function writeIntLE( + value, + offset, + byteLength, +) { + if (byteLength === 6) { + return writeU_Int48LE( + this, + value, + offset, + -0x800000000000, + 0x7fffffffffff, + ); + } + if (byteLength === 5) { + return writeU_Int40LE(this, value, offset, -0x8000000000, 0x7fffffffff); + } + if (byteLength === 3) { + return writeU_Int24LE(this, value, offset, -0x800000, 0x7fffff); + } + if (byteLength === 4) { + return writeU_Int32LE(this, value, offset, -0x80000000, 0x7fffffff); + } + if (byteLength === 2) { + return writeU_Int16LE(this, value, offset, -0x8000, 0x7fff); + } + if (byteLength === 1) { + return writeU_Int8(this, value, offset, -0x80, 0x7f); + } + + boundsError(byteLength, 6, "byteLength"); +}; + +Buffer.prototype.writeIntBE = function writeIntBE( + value, + offset, + byteLength, +) { + if (byteLength === 6) { + return writeU_Int48BE( + this, + value, + offset, + -0x800000000000, + 0x7fffffffffff, + ); + } + if (byteLength === 5) { + return writeU_Int40BE(this, value, offset, -0x8000000000, 0x7fffffffff); + } + if (byteLength === 3) { + return writeU_Int24BE(this, value, offset, -0x800000, 0x7fffff); + } + if (byteLength === 4) { + return writeU_Int32BE(this, value, offset, -0x80000000, 0x7fffffff); + } + if (byteLength === 2) { + return writeU_Int16BE(this, value, offset, -0x8000, 0x7fff); + } + if (byteLength === 1) { + return writeU_Int8(this, value, offset, -0x80, 0x7f); + } + + boundsError(byteLength, 6, "byteLength"); +}; + +Buffer.prototype.writeInt8 = function writeInt8(value, offset = 0) { + return writeU_Int8(this, value, offset, -0x80, 0x7f); +}; + +Buffer.prototype.writeInt16LE = function writeInt16LE(value, offset = 0) { + return writeU_Int16LE(this, value, offset, -0x8000, 0x7fff); +}; + +Buffer.prototype.writeInt16BE = function writeInt16BE( + value, + offset = 0, +) { + return writeU_Int16BE(this, value, offset, -0x8000, 0x7fff); +}; + +Buffer.prototype.writeInt32LE = function writeInt32LE(value, offset = 0) { + return writeU_Int32LE(this, value, offset, -0x80000000, 0x7fffffff); +}; + +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.writeBigInt64BE = defineBigIntMethod( + function writeBigInt64BE(value, offset = 0) { + return wrtBigUInt64BE( + this, + value, + offset, + -BigInt("0x8000000000000000"), + BigInt("0x7fffffffffffffff"), + ); + }, +); + +Buffer.prototype.writeFloatLE = function writeFloatLE( + value, + offset, +) { + return bigEndian + ? writeFloatBackwards(this, value, offset) + : writeFloatForwards(this, value, offset); +}; + +Buffer.prototype.writeFloatBE = function writeFloatBE( + value, + offset, +) { + return bigEndian + ? writeFloatForwards(this, value, offset) + : writeFloatBackwards(this, value, offset); +}; + +Buffer.prototype.writeDoubleLE = function writeDoubleLE( + value, + offset, +) { + return bigEndian + ? writeDoubleBackwards(this, value, offset) + : writeDoubleForwards(this, value, offset); +}; + +Buffer.prototype.writeDoubleBE = function writeDoubleBE( + value, + offset, +) { + return bigEndian + ? writeDoubleForwards(this, value, offset) + : writeDoubleBackwards(this, value, offset); +}; + +Buffer.prototype.copy = function copy( + target, + targetStart, + sourceStart, + sourceEnd, +) { + if (!isUint8Array(this)) { + throw new codes.ERR_INVALID_ARG_TYPE( + "source", + ["Buffer", "Uint8Array"], + this, + ); + } + + if (!isUint8Array(target)) { + throw new codes.ERR_INVALID_ARG_TYPE( + "target", + ["Buffer", "Uint8Array"], + target, + ); + } + + if (targetStart === undefined) { + targetStart = 0; + } else { + targetStart = toInteger(targetStart, 0); + if (targetStart < 0) { + throw new codes.ERR_OUT_OF_RANGE("targetStart", ">= 0", targetStart); + } + } + + if (sourceStart === undefined) { + sourceStart = 0; + } else { + sourceStart = toInteger(sourceStart, 0); + if (sourceStart < 0) { + throw new codes.ERR_OUT_OF_RANGE("sourceStart", ">= 0", sourceStart); + } + if (sourceStart >= MAX_UINT32) { + throw new codes.ERR_OUT_OF_RANGE( + "sourceStart", + `< ${MAX_UINT32}`, + sourceStart, + ); + } + } + + if (sourceEnd === undefined) { + sourceEnd = this.length; + } else { + sourceEnd = toInteger(sourceEnd, 0); + if (sourceEnd < 0) { + throw new codes.ERR_OUT_OF_RANGE("sourceEnd", ">= 0", sourceEnd); + } + if (sourceEnd >= MAX_UINT32) { + throw new codes.ERR_OUT_OF_RANGE( + "sourceEnd", + `< ${MAX_UINT32}`, + sourceEnd, + ); + } + } + + if (targetStart >= target.length) { + return 0; + } + + if (sourceEnd > 0 && sourceEnd < sourceStart) { + sourceEnd = sourceStart; + } + if (sourceEnd === sourceStart) { + return 0; + } + if (target.length === 0 || this.length === 0) { + return 0; + } + + if (sourceEnd > this.length) { + sourceEnd = this.length; + } + + if (target.length - targetStart < sourceEnd - sourceStart) { + sourceEnd = target.length - targetStart + sourceStart; + } + + const len = sourceEnd - sourceStart; + if ( + this === target && typeof Uint8Array.prototype.copyWithin === "function" + ) { + this.copyWithin(targetStart, sourceStart, sourceEnd); + } else { + Uint8Array.prototype.set.call( + target, + this.subarray(sourceStart, sourceEnd), + targetStart, + ); + } + return len; +}; + +Buffer.prototype.fill = function fill(val, start, end, encoding) { + if (typeof val === "string") { + if (typeof start === "string") { + encoding = start; + start = 0; + end = this.length; + } else if (typeof end === "string") { + encoding = end; + end = this.length; + } + if (encoding !== void 0 && typeof encoding !== "string") { + throw new TypeError("encoding must be a string"); + } + if (typeof encoding === "string" && !Buffer.isEncoding(encoding)) { + throw new TypeError("Unknown encoding: " + encoding); + } + if (val.length === 1) { + const code = val.charCodeAt(0); + if (encoding === "utf8" && code < 128 || encoding === "latin1") { + val = code; + } + } + } else if (typeof val === "number") { + val = val & 255; + } else if (typeof val === "boolean") { + val = Number(val); + } + if (start < 0 || this.length < start || this.length < end) { + throw new RangeError("Out of range index"); + } + if (end <= start) { + return this; + } + start = start >>> 0; + end = end === void 0 ? this.length : end >>> 0; + if (!val) { + val = 0; + } + let i; + if (typeof val === "number") { + for (i = start; i < end; ++i) { + this[i] = val; + } + } else { + const bytes = Buffer.isBuffer(val) ? val : Buffer.from(val, encoding); + const len = bytes.length; + if (len === 0) { + throw new codes.ERR_INVALID_ARG_VALUE( + "value", + val, + ); + } + for (i = 0; i < end - start; ++i) { + this[i + start] = bytes[i % len]; + } + } + return this; +}; + +function checkBounds(buf, offset, byteLength2) { + validateNumber(offset, "offset"); + if (buf[offset] === void 0 || buf[offset + byteLength2] === void 0) { + boundsError(offset, buf.length - (byteLength2 + 1)); + } +} + +function checkIntBI(value, min, max, buf, offset, byteLength2) { + if (value > max || value < min) { + const n = typeof min === "bigint" ? "n" : ""; + let range; + if (byteLength2 > 3) { + if (min === 0 || min === BigInt(0)) { + range = `>= 0${n} and < 2${n} ** ${(byteLength2 + 1) * 8}${n}`; + } else { + range = `>= -(2${n} ** ${(byteLength2 + 1) * 8 - 1}${n}) and < 2 ** ${ + (byteLength2 + 1) * 8 - 1 + }${n}`; + } + } else { + range = `>= ${min}${n} and <= ${max}${n}`; + } + throw new codes.ERR_OUT_OF_RANGE("value", range, value); + } + checkBounds(buf, offset, byteLength2); +} + +function utf8ToBytes(string, units) { + units = units || Infinity; + let codePoint; + const length = string.length; + let leadSurrogate = null; + const bytes = []; + for (let i = 0; i < length; ++i) { + codePoint = string.charCodeAt(i); + if (codePoint > 55295 && codePoint < 57344) { + if (!leadSurrogate) { + if (codePoint > 56319) { + if ((units -= 3) > -1) { + bytes.push(239, 191, 189); + } + continue; + } else if (i + 1 === length) { + if ((units -= 3) > -1) { + bytes.push(239, 191, 189); + } + continue; + } + leadSurrogate = codePoint; + continue; + } + if (codePoint < 56320) { + if ((units -= 3) > -1) { + bytes.push(239, 191, 189); + } + leadSurrogate = codePoint; + continue; + } + codePoint = (leadSurrogate - 55296 << 10 | codePoint - 56320) + 65536; + } else if (leadSurrogate) { + if ((units -= 3) > -1) { + bytes.push(239, 191, 189); + } + } + leadSurrogate = null; + if (codePoint < 128) { + if ((units -= 1) < 0) { + break; + } + bytes.push(codePoint); + } else if (codePoint < 2048) { + if ((units -= 2) < 0) { + break; + } + bytes.push(codePoint >> 6 | 192, codePoint & 63 | 128); + } else if (codePoint < 65536) { + if ((units -= 3) < 0) { + break; + } + bytes.push( + codePoint >> 12 | 224, + codePoint >> 6 & 63 | 128, + codePoint & 63 | 128, + ); + } else if (codePoint < 1114112) { + if ((units -= 4) < 0) { + break; + } + bytes.push( + codePoint >> 18 | 240, + codePoint >> 12 & 63 | 128, + codePoint >> 6 & 63 | 128, + codePoint & 63 | 128, + ); + } else { + throw new Error("Invalid code point"); + } + } + return bytes; +} + +function blitBuffer(src, dst, offset, byteLength) { + let i; + const length = byteLength === undefined ? src.length : byteLength; + for (i = 0; i < length; ++i) { + if (i + offset >= dst.length || i >= src.length) { + break; + } + dst[i + offset] = src[i]; + } + return i; +} + +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); + for (let i = 0; i < 16; ++i) { + const i16 = i * 16; + for (let j = 0; j < 16; ++j) { + table[i16 + j] = alphabet[i] + alphabet[j]; + } + } + return table; +}(); + +function defineBigIntMethod(fn) { + return typeof BigInt === "undefined" ? BufferBigIntNotDefined : fn; +} + +function BufferBigIntNotDefined() { + throw new Error("BigInt not supported"); +} + +export const atob = globalThis.atob; +export const Blob = globalThis.Blob; +export const btoa = globalThis.btoa; + +export function readUInt48LE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 5]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 6); + } + + return first + + buf[++offset] * 2 ** 8 + + buf[++offset] * 2 ** 16 + + buf[++offset] * 2 ** 24 + + (buf[++offset] + last * 2 ** 8) * 2 ** 32; +} + +export function readUInt40LE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 4]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 5); + } + + return first + + buf[++offset] * 2 ** 8 + + buf[++offset] * 2 ** 16 + + buf[++offset] * 2 ** 24 + + last * 2 ** 32; +} + +export function readUInt24LE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 2]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 3); + } + + return first + buf[++offset] * 2 ** 8 + last * 2 ** 16; +} + +export function readUInt48BE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 5]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 6); + } + + return (first * 2 ** 8 + buf[++offset]) * 2 ** 32 + + buf[++offset] * 2 ** 24 + + buf[++offset] * 2 ** 16 + + buf[++offset] * 2 ** 8 + + last; +} + +export function readUInt40BE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 4]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 5); + } + + return first * 2 ** 32 + + buf[++offset] * 2 ** 24 + + buf[++offset] * 2 ** 16 + + buf[++offset] * 2 ** 8 + + last; +} + +export function readUInt24BE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 2]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 3); + } + + return first * 2 ** 16 + buf[++offset] * 2 ** 8 + last; +} + +export function readUInt16BE(offset = 0) { + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 1]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 2); + } + + return first * 2 ** 8 + last; +} + +export function readUInt32BE(offset = 0) { + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 3]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 4); + } + + return first * 2 ** 24 + + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 8 + + last; +} + +export function readDoubleBackwards(buffer, offset = 0) { + validateNumber(offset, "offset"); + const first = buffer[offset]; + const last = buffer[offset + 7]; + if (first === undefined || last === undefined) { + boundsError(offset, buffer.length - 8); + } + + uInt8Float64Array[7] = first; + uInt8Float64Array[6] = buffer[++offset]; + uInt8Float64Array[5] = buffer[++offset]; + uInt8Float64Array[4] = buffer[++offset]; + uInt8Float64Array[3] = buffer[++offset]; + uInt8Float64Array[2] = buffer[++offset]; + uInt8Float64Array[1] = buffer[++offset]; + uInt8Float64Array[0] = last; + return float64Array[0]; +} + +export function readDoubleForwards(buffer, offset = 0) { + validateNumber(offset, "offset"); + const first = buffer[offset]; + const last = buffer[offset + 7]; + if (first === undefined || last === undefined) { + boundsError(offset, buffer.length - 8); + } + + uInt8Float64Array[0] = first; + uInt8Float64Array[1] = buffer[++offset]; + uInt8Float64Array[2] = buffer[++offset]; + uInt8Float64Array[3] = buffer[++offset]; + uInt8Float64Array[4] = buffer[++offset]; + uInt8Float64Array[5] = buffer[++offset]; + uInt8Float64Array[6] = buffer[++offset]; + uInt8Float64Array[7] = last; + return float64Array[0]; +} + +export function writeDoubleForwards(buffer, val, offset = 0) { + val = +val; + checkBounds(buffer, offset, 7); + + float64Array[0] = val; + buffer[offset++] = uInt8Float64Array[0]; + buffer[offset++] = uInt8Float64Array[1]; + buffer[offset++] = uInt8Float64Array[2]; + buffer[offset++] = uInt8Float64Array[3]; + buffer[offset++] = uInt8Float64Array[4]; + buffer[offset++] = uInt8Float64Array[5]; + buffer[offset++] = uInt8Float64Array[6]; + buffer[offset++] = uInt8Float64Array[7]; + return offset; +} + +export function writeDoubleBackwards(buffer, val, offset = 0) { + val = +val; + checkBounds(buffer, offset, 7); + + float64Array[0] = val; + buffer[offset++] = uInt8Float64Array[7]; + buffer[offset++] = uInt8Float64Array[6]; + buffer[offset++] = uInt8Float64Array[5]; + buffer[offset++] = uInt8Float64Array[4]; + buffer[offset++] = uInt8Float64Array[3]; + buffer[offset++] = uInt8Float64Array[2]; + buffer[offset++] = uInt8Float64Array[1]; + buffer[offset++] = uInt8Float64Array[0]; + return offset; +} + +export function readFloatBackwards(buffer, offset = 0) { + validateNumber(offset, "offset"); + const first = buffer[offset]; + const last = buffer[offset + 3]; + if (first === undefined || last === undefined) { + boundsError(offset, buffer.length - 4); + } + + uInt8Float32Array[3] = first; + uInt8Float32Array[2] = buffer[++offset]; + uInt8Float32Array[1] = buffer[++offset]; + uInt8Float32Array[0] = last; + return float32Array[0]; +} + +export function readFloatForwards(buffer, offset = 0) { + validateNumber(offset, "offset"); + const first = buffer[offset]; + const last = buffer[offset + 3]; + if (first === undefined || last === undefined) { + boundsError(offset, buffer.length - 4); + } + + uInt8Float32Array[0] = first; + uInt8Float32Array[1] = buffer[++offset]; + uInt8Float32Array[2] = buffer[++offset]; + uInt8Float32Array[3] = last; + return float32Array[0]; +} + +export function writeFloatForwards(buffer, val, offset = 0) { + val = +val; + checkBounds(buffer, offset, 3); + + float32Array[0] = val; + buffer[offset++] = uInt8Float32Array[0]; + buffer[offset++] = uInt8Float32Array[1]; + buffer[offset++] = uInt8Float32Array[2]; + buffer[offset++] = uInt8Float32Array[3]; + return offset; +} + +export function writeFloatBackwards(buffer, val, offset = 0) { + val = +val; + checkBounds(buffer, offset, 3); + + float32Array[0] = val; + buffer[offset++] = uInt8Float32Array[3]; + buffer[offset++] = uInt8Float32Array[2]; + buffer[offset++] = uInt8Float32Array[1]; + buffer[offset++] = uInt8Float32Array[0]; + return offset; +} + +export function readInt24LE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 2]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 3); + } + + const val = first + buf[++offset] * 2 ** 8 + last * 2 ** 16; + return val | (val & 2 ** 23) * 0x1fe; +} + +export function readInt40LE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 4]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 5); + } + + return (last | (last & 2 ** 7) * 0x1fffffe) * 2 ** 32 + + first + + buf[++offset] * 2 ** 8 + + buf[++offset] * 2 ** 16 + + buf[++offset] * 2 ** 24; +} + +export function readInt48LE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 5]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 6); + } + + const val = buf[offset + 4] + last * 2 ** 8; + return (val | (val & 2 ** 15) * 0x1fffe) * 2 ** 32 + + first + + buf[++offset] * 2 ** 8 + + buf[++offset] * 2 ** 16 + + buf[++offset] * 2 ** 24; +} + +export function readInt24BE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 2]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 3); + } + + const val = first * 2 ** 16 + buf[++offset] * 2 ** 8 + last; + return val | (val & 2 ** 23) * 0x1fe; +} + +export function readInt48BE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 5]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 6); + } + + const val = buf[++offset] + first * 2 ** 8; + return (val | (val & 2 ** 15) * 0x1fffe) * 2 ** 32 + + buf[++offset] * 2 ** 24 + + buf[++offset] * 2 ** 16 + + buf[++offset] * 2 ** 8 + + last; +} + +export function readInt40BE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 4]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 5); + } + + return (first | (first & 2 ** 7) * 0x1fffffe) * 2 ** 32 + + buf[++offset] * 2 ** 24 + + buf[++offset] * 2 ** 16 + + buf[++offset] * 2 ** 8 + + last; +} + +export function byteLengthUtf8(str) { + return utf8Encoder.encode(str).length; +} + +function base64ByteLength(str, bytes) { + // Handle padding + if (str.charCodeAt(bytes - 1) === 0x3D) { + bytes--; + } + if (bytes > 1 && str.charCodeAt(bytes - 1) === 0x3D) { + bytes--; + } + + // Base64 ratio: 3/4 + return (bytes * 3) >>> 2; +} + +export const encodingsMap = Object.create(null); +for (let i = 0; i < encodings.length; ++i) { + encodingsMap[encodings[i]] = i; +} + +export const encodingOps = { + ascii: { + byteLength: (string) => string.length, + encoding: "ascii", + encodingVal: encodingsMap.ascii, + indexOf: (buf, val, byteOffset, dir) => + indexOfBuffer( + buf, + asciiToBytes(val), + byteOffset, + encodingsMap.ascii, + dir, + ), + slice: (buf, start, end) => buf.asciiSlice(start, end), + write: (buf, string, offset, len) => buf.asciiWrite(string, offset, len), + }, + base64: { + byteLength: (string) => base64ByteLength(string, string.length), + encoding: "base64", + encodingVal: encodingsMap.base64, + indexOf: (buf, val, byteOffset, dir) => + indexOfBuffer( + buf, + base64ToBytes(val), + byteOffset, + encodingsMap.base64, + dir, + ), + slice: (buf, start, end) => buf.base64Slice(start, end), + write: (buf, string, offset, len) => buf.base64Write(string, offset, len), + }, + base64url: { + byteLength: (string) => base64ByteLength(string, string.length), + encoding: "base64url", + encodingVal: encodingsMap.base64url, + indexOf: (buf, val, byteOffset, dir) => + indexOfBuffer( + buf, + base64UrlToBytes(val), + byteOffset, + encodingsMap.base64url, + dir, + ), + slice: (buf, start, end) => buf.base64urlSlice(start, end), + write: (buf, string, offset, len) => + buf.base64urlWrite(string, offset, len), + }, + hex: { + byteLength: (string) => string.length >>> 1, + encoding: "hex", + encodingVal: encodingsMap.hex, + indexOf: (buf, val, byteOffset, dir) => + indexOfBuffer( + buf, + hexToBytes(val), + byteOffset, + encodingsMap.hex, + dir, + ), + slice: (buf, start, end) => buf.hexSlice(start, end), + write: (buf, string, offset, len) => buf.hexWrite(string, offset, len), + }, + latin1: { + byteLength: (string) => string.length, + encoding: "latin1", + encodingVal: encodingsMap.latin1, + indexOf: (buf, val, byteOffset, dir) => + indexOfBuffer( + buf, + asciiToBytes(val), + byteOffset, + encodingsMap.latin1, + dir, + ), + slice: (buf, start, end) => buf.latin1Slice(start, end), + write: (buf, string, offset, len) => buf.latin1Write(string, offset, len), + }, + ucs2: { + byteLength: (string) => string.length * 2, + encoding: "ucs2", + encodingVal: encodingsMap.utf16le, + indexOf: (buf, val, byteOffset, dir) => + indexOfBuffer( + buf, + utf16leToBytes(val), + byteOffset, + encodingsMap.utf16le, + dir, + ), + slice: (buf, start, end) => buf.ucs2Slice(start, end), + write: (buf, string, offset, len) => buf.ucs2Write(string, offset, len), + }, + utf8: { + byteLength: byteLengthUtf8, + encoding: "utf8", + encodingVal: encodingsMap.utf8, + indexOf: (buf, val, byteOffset, dir) => + indexOfBuffer( + buf, + utf8Encoder.encode(val), + byteOffset, + encodingsMap.utf8, + dir, + ), + slice: (buf, start, end) => buf.utf8Slice(start, end), + write: (buf, string, offset, len) => buf.utf8Write(string, offset, len), + }, + utf16le: { + byteLength: (string) => string.length * 2, + encoding: "utf16le", + encodingVal: encodingsMap.utf16le, + indexOf: (buf, val, byteOffset, dir) => + indexOfBuffer( + buf, + utf16leToBytes(val), + byteOffset, + encodingsMap.utf16le, + dir, + ), + slice: (buf, start, end) => buf.ucs2Slice(start, end), + write: (buf, string, offset, len) => buf.ucs2Write(string, offset, len), + }, +}; + +export function getEncodingOps(encoding) { + encoding = String(encoding).toLowerCase(); + switch (encoding.length) { + case 4: + if (encoding === "utf8") return encodingOps.utf8; + if (encoding === "ucs2") return encodingOps.ucs2; + break; + case 5: + if (encoding === "utf-8") return encodingOps.utf8; + if (encoding === "ascii") return encodingOps.ascii; + if (encoding === "ucs-2") return encodingOps.ucs2; + break; + case 7: + if (encoding === "utf16le") { + return encodingOps.utf16le; + } + break; + case 8: + if (encoding === "utf-16le") { + return encodingOps.utf16le; + } + break; + // deno-lint-ignore no-fallthrough + case 6: + if (encoding === "latin1" || encoding === "binary") { + return encodingOps.latin1; + } + if (encoding === "base64") return encodingOps.base64; + case 3: + if (encoding === "hex") { + return encodingOps.hex; + } + break; + case 9: + if (encoding === "base64url") { + return encodingOps.base64url; + } + break; + } +} + +export function _copyActual( + source, + target, + targetStart, + sourceStart, + sourceEnd, +) { + if (sourceEnd - sourceStart > target.length - targetStart) { + sourceEnd = sourceStart + target.length - targetStart; + } + + let nb = sourceEnd - sourceStart; + const sourceLen = source.length - sourceStart; + if (nb > sourceLen) { + nb = sourceLen; + } + + if (sourceStart !== 0 || sourceEnd < source.length) { + source = new Uint8Array(source.buffer, source.byteOffset + sourceStart, nb); + } + + target.set(source, targetStart); + + return nb; +} + +export function boundsError(value, length, type) { + if (Math.floor(value) !== value) { + validateNumber(value, type); + throw new codes.ERR_OUT_OF_RANGE(type || "offset", "an integer", value); + } + + if (length < 0) { + throw new codes.ERR_BUFFER_OUT_OF_BOUNDS(); + } + + throw new codes.ERR_OUT_OF_RANGE( + type || "offset", + `>= ${type ? 1 : 0} and <= ${length}`, + value, + ); +} + +export function validateNumber(value, name) { + if (typeof value !== "number") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value); + } +} + +function checkInt(value, min, max, buf, offset, byteLength) { + if (value > max || value < min) { + const n = typeof min === "bigint" ? "n" : ""; + let range; + if (byteLength > 3) { + if (min === 0 || min === 0n) { + range = `>= 0${n} and < 2${n} ** ${(byteLength + 1) * 8}${n}`; + } else { + range = `>= -(2${n} ** ${(byteLength + 1) * 8 - 1}${n}) and ` + + `< 2${n} ** ${(byteLength + 1) * 8 - 1}${n}`; + } + } else { + range = `>= ${min}${n} and <= ${max}${n}`; + } + throw new codes.ERR_OUT_OF_RANGE("value", range, value); + } + checkBounds(buf, offset, byteLength); +} + +export function toInteger(n, defaultVal) { + n = +n; + if ( + !Number.isNaN(n) && + n >= Number.MIN_SAFE_INTEGER && + n <= Number.MAX_SAFE_INTEGER + ) { + return ((n % 1) === 0 ? n : Math.floor(n)); + } + return defaultVal; +} + +// deno-lint-ignore camelcase +export function writeU_Int8(buf, value, offset, min, max) { + value = +value; + validateNumber(offset, "offset"); + if (value > max || value < min) { + throw new codes.ERR_OUT_OF_RANGE("value", `>= ${min} and <= ${max}`, value); + } + if (buf[offset] === undefined) { + boundsError(offset, buf.length - 1); + } + + buf[offset] = value; + return offset + 1; +} + +// deno-lint-ignore camelcase +export function writeU_Int16BE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 1); + + buf[offset++] = value >>> 8; + buf[offset++] = value; + return offset; +} + +export function _writeUInt32LE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 3); + + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + return offset; +} + +// deno-lint-ignore camelcase +export function writeU_Int16LE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 1); + + buf[offset++] = value; + buf[offset++] = value >>> 8; + return offset; +} + +export function _writeUInt32BE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 3); + + buf[offset + 3] = value; + value = value >>> 8; + buf[offset + 2] = value; + value = value >>> 8; + buf[offset + 1] = value; + value = value >>> 8; + buf[offset] = value; + return offset + 4; +} + +// deno-lint-ignore camelcase +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); + buf[offset++] = newVal >>> 8; + buf[offset++] = newVal; + buf[offset + 3] = value; + value = value >>> 8; + buf[offset + 2] = value; + value = value >>> 8; + buf[offset + 1] = value; + value = value >>> 8; + buf[offset] = value; + return offset + 4; +} + +// deno-lint-ignore camelcase +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 + 3] = value; + value = value >>> 8; + buf[offset + 2] = value; + value = value >>> 8; + buf[offset + 1] = value; + value = value >>> 8; + buf[offset] = value; + return offset + 4; +} + +// deno-lint-ignore camelcase +export function writeU_Int32BE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 3); + + buf[offset + 3] = value; + value = value >>> 8; + buf[offset + 2] = value; + value = value >>> 8; + buf[offset + 1] = value; + value = value >>> 8; + buf[offset] = value; + return offset + 4; +} + +// deno-lint-ignore camelcase +export function writeU_Int24BE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 2); + + buf[offset + 2] = value; + value = value >>> 8; + buf[offset + 1] = value; + value = value >>> 8; + buf[offset] = value; + return offset + 3; +} + +export function validateOffset( + value, + name, + min = 0, + max = Number.MAX_SAFE_INTEGER, +) { + if (typeof value !== "number") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value); + } + if (!Number.isInteger(value)) { + throw new codes.ERR_OUT_OF_RANGE(name, "an integer", value); + } + if (value < min || value > max) { + throw new codes.ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); + } +} + +// deno-lint-ignore camelcase +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); + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + buf[offset++] = newVal; + buf[offset++] = newVal >>> 8; + return offset; +} + +// deno-lint-ignore camelcase +export function writeU_Int40LE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 4); + + const newVal = value; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + buf[offset++] = Math.floor(newVal * 2 ** -32); + return offset; +} + +// deno-lint-ignore camelcase +export function writeU_Int32LE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 3); + + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + return offset; +} + +// deno-lint-ignore camelcase +export function writeU_Int24LE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 2); + + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + return offset; +} + +export default { + atob, + btoa, + Blob, + Buffer, + constants, + kMaxLength, + kStringMaxLength, + SlowBuffer, +}; diff --git a/ext/node/polyfills/internal/child_process.ts b/ext/node/polyfills/internal/child_process.ts new file mode 100644 index 000000000..92aa8d4fa --- /dev/null +++ b/ext/node/polyfills/internal/child_process.ts @@ -0,0 +1,1026 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// This module implements 'child_process' module of Node.JS API. +// ref: https://nodejs.org/api/child_process.html +import { assert } from "internal:deno_node/polyfills/_util/asserts.ts"; +import { EventEmitter } from "internal:deno_node/polyfills/events.ts"; +import { os } from "internal:deno_node/polyfills/internal_binding/constants.ts"; +import { + notImplemented, + warnNotImplemented, +} from "internal:deno_node/polyfills/_utils.ts"; +import { + Readable, + Stream, + Writable, +} from "internal:deno_node/polyfills/stream.ts"; +import { deferred } from "internal:deno_node/polyfills/_util/async.ts"; +import { isWindows } from "internal:deno_node/polyfills/_util/os.ts"; +import { nextTick } from "internal:deno_node/polyfills/_next_tick.ts"; +import { + AbortError, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_UNKNOWN_SIGNAL, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { errnoException } from "internal:deno_node/polyfills/internal/errors.ts"; +import { ErrnoException } from "internal:deno_node/polyfills/_global.d.ts"; +import { codeMap } from "internal:deno_node/polyfills/internal_binding/uv.ts"; +import { + isInt32, + validateBoolean, + validateObject, + validateString, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { + ArrayIsArray, + ArrayPrototypeFilter, + ArrayPrototypeJoin, + ArrayPrototypePush, + ArrayPrototypeSlice, + ArrayPrototypeSort, + ArrayPrototypeUnshift, + ObjectPrototypeHasOwnProperty, + StringPrototypeToUpperCase, +} from "internal:deno_node/polyfills/internal/primordials.mjs"; +import { kEmptyObject } from "internal:deno_node/polyfills/internal/util.mjs"; +import { getValidatedPath } from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import process from "internal:deno_node/polyfills/process.ts"; + +export function mapValues<T, O>( + record: Readonly<Record<string, T>>, + transformer: (value: T) => O, +): Record<string, O> { + const ret: Record<string, O> = {}; + const entries = Object.entries(record); + + for (const [key, value] of entries) { + const mappedValue = transformer(value); + + ret[key] = mappedValue; + } + + return ret; +} + +type NodeStdio = "pipe" | "overlapped" | "ignore" | "inherit" | "ipc"; +type DenoStdio = "inherit" | "piped" | "null"; + +// @ts-ignore Deno[Deno.internal] is used on purpose here +const DenoCommand = Deno[Deno.internal]?.nodeUnstable?.Command || + Deno.Command; + +export function stdioStringToArray( + stdio: NodeStdio, + channel: NodeStdio | number, +) { + const options: (NodeStdio | number)[] = []; + + switch (stdio) { + case "ignore": + case "overlapped": + case "pipe": + options.push(stdio, stdio, stdio); + break; + case "inherit": + options.push(stdio, stdio, stdio); + break; + default: + throw new ERR_INVALID_ARG_VALUE("stdio", stdio); + } + + if (channel) options.push(channel); + + return options; +} + +export class ChildProcess extends EventEmitter { + /** + * The exit code of the child process. This property will be `null` until the child process exits. + */ + exitCode: number | null = null; + + /** + * This property is set to `true` after `kill()` is called. + */ + killed = false; + + /** + * The PID of this child process. + */ + pid!: number; + + /** + * The signal received by this child process. + */ + signalCode: string | null = null; + + /** + * Command line arguments given to this child process. + */ + spawnargs: string[]; + + /** + * The executable file name of this child process. + */ + spawnfile: string; + + /** + * This property represents the child process's stdin. + */ + stdin: Writable | null = null; + + /** + * This property represents the child process's stdout. + */ + stdout: Readable | null = null; + + /** + * This property represents the child process's stderr. + */ + stderr: Readable | null = null; + + /** + * Pipes to this child process. + */ + stdio: [Writable | null, Readable | null, Readable | null] = [ + null, + null, + null, + ]; + + #process!: Deno.ChildProcess; + #spawned = deferred<void>(); + + constructor( + command: string, + args?: string[], + options?: ChildProcessOptions, + ) { + super(); + + const { + env = {}, + stdio = ["pipe", "pipe", "pipe"], + cwd, + shell = false, + signal, + windowsVerbatimArguments = false, + } = options || {}; + const [ + stdin = "pipe", + stdout = "pipe", + stderr = "pipe", + _channel, // TODO(kt3k): handle this correctly + ] = normalizeStdioOption(stdio); + const [cmd, cmdArgs] = buildCommand( + command, + args || [], + shell, + ); + this.spawnfile = cmd; + this.spawnargs = [cmd, ...cmdArgs]; + + const stringEnv = mapValues(env, (value) => value.toString()); + + try { + this.#process = new DenoCommand(cmd, { + args: cmdArgs, + cwd, + env: stringEnv, + stdin: toDenoStdio(stdin as NodeStdio | number), + stdout: toDenoStdio(stdout as NodeStdio | number), + stderr: toDenoStdio(stderr as NodeStdio | number), + windowsRawArguments: windowsVerbatimArguments, + }).spawn(); + this.pid = this.#process.pid; + + if (stdin === "pipe") { + assert(this.#process.stdin); + this.stdin = Writable.fromWeb(this.#process.stdin); + } + + if (stdout === "pipe") { + assert(this.#process.stdout); + this.stdout = Readable.fromWeb(this.#process.stdout); + } + + if (stderr === "pipe") { + assert(this.#process.stderr); + this.stderr = Readable.fromWeb(this.#process.stderr); + } + + this.stdio[0] = this.stdin; + this.stdio[1] = this.stdout; + this.stdio[2] = this.stderr; + + nextTick(() => { + this.emit("spawn"); + this.#spawned.resolve(); + }); + + if (signal) { + const onAbortListener = () => { + try { + if (this.kill("SIGKILL")) { + this.emit("error", new AbortError()); + } + } catch (err) { + this.emit("error", err); + } + }; + if (signal.aborted) { + nextTick(onAbortListener); + } else { + signal.addEventListener("abort", onAbortListener, { once: true }); + this.addListener( + "exit", + () => signal.removeEventListener("abort", onAbortListener), + ); + } + } + + (async () => { + const status = await this.#process.status; + this.exitCode = status.code; + this.#spawned.then(async () => { + const exitCode = this.signalCode == null ? this.exitCode : null; + const signalCode = this.signalCode == null ? null : this.signalCode; + // The 'exit' and 'close' events must be emitted after the 'spawn' event. + this.emit("exit", exitCode, signalCode); + await this.#_waitForChildStreamsToClose(); + this.#closePipes(); + this.emit("close", exitCode, signalCode); + }); + })(); + } catch (err) { + this.#_handleError(err); + } + } + + /** + * @param signal NOTE: this parameter is not yet implemented. + */ + kill(signal?: number | string): boolean { + if (this.killed) { + return this.killed; + } + + const denoSignal = signal == null ? "SIGTERM" : toDenoSignal(signal); + this.#closePipes(); + try { + this.#process.kill(denoSignal); + } catch (err) { + const alreadyClosed = err instanceof TypeError || + err instanceof Deno.errors.PermissionDenied; + if (!alreadyClosed) { + throw err; + } + } + this.killed = true; + this.signalCode = denoSignal; + return this.killed; + } + + ref() { + this.#process.ref(); + } + + unref() { + this.#process.unref(); + } + + disconnect() { + warnNotImplemented("ChildProcess.prototype.disconnect"); + } + + async #_waitForChildStreamsToClose() { + const promises = [] as Array<Promise<void>>; + if (this.stdin && !this.stdin.destroyed) { + assert(this.stdin); + this.stdin.destroy(); + promises.push(waitForStreamToClose(this.stdin)); + } + if (this.stdout && !this.stdout.destroyed) { + promises.push(waitForReadableToClose(this.stdout)); + } + if (this.stderr && !this.stderr.destroyed) { + promises.push(waitForReadableToClose(this.stderr)); + } + await Promise.all(promises); + } + + #_handleError(err: unknown) { + nextTick(() => { + this.emit("error", err); // TODO(uki00a) Convert `err` into nodejs's `SystemError` class. + }); + } + + #closePipes() { + if (this.stdin) { + assert(this.stdin); + this.stdin.destroy(); + } + } +} + +const supportedNodeStdioTypes: NodeStdio[] = ["pipe", "ignore", "inherit"]; +function toDenoStdio( + pipe: NodeStdio | number | Stream | null | undefined, +): DenoStdio { + if ( + !supportedNodeStdioTypes.includes(pipe as NodeStdio) || + typeof pipe === "number" || pipe instanceof Stream + ) { + notImplemented(`toDenoStdio pipe=${typeof pipe} (${pipe})`); + } + switch (pipe) { + case "pipe": + case undefined: + case null: + return "piped"; + case "ignore": + return "null"; + case "inherit": + return "inherit"; + default: + notImplemented(`toDenoStdio pipe=${typeof pipe} (${pipe})`); + } +} + +function toDenoSignal(signal: number | string): Deno.Signal { + if (typeof signal === "number") { + for (const name of keys(os.signals)) { + if (os.signals[name] === signal) { + return name as Deno.Signal; + } + } + throw new ERR_UNKNOWN_SIGNAL(String(signal)); + } + + const denoSignal = signal as Deno.Signal; + if (denoSignal in os.signals) { + return denoSignal; + } + throw new ERR_UNKNOWN_SIGNAL(signal); +} + +function keys<T extends Record<string, unknown>>(object: T): Array<keyof T> { + return Object.keys(object); +} + +export interface ChildProcessOptions { + /** + * Current working directory of the child process. + */ + cwd?: string | URL; + + /** + * Environment variables passed to the child process. + */ + env?: Record<string, string | number | boolean>; + + /** + * This option defines child process's stdio configuration. + * @see https://nodejs.org/api/child_process.html#child_process_options_stdio + */ + stdio?: Array<NodeStdio | number | Stream | null | undefined> | NodeStdio; + + /** + * NOTE: This option is not yet implemented. + */ + detached?: boolean; + + /** + * NOTE: This option is not yet implemented. + */ + uid?: number; + + /** + * NOTE: This option is not yet implemented. + */ + gid?: number; + + /** + * NOTE: This option is not yet implemented. + */ + argv0?: string; + + /** + * * If this option is `true`, run the command in the shell. + * * If this option is a string, run the command in the specified shell. + */ + shell?: string | boolean; + + /** + * Allows aborting the child process using an AbortSignal. + */ + signal?: AbortSignal; + + /** + * NOTE: This option is not yet implemented. + */ + serialization?: "json" | "advanced"; + + /** No quoting or escaping of arguments is done on Windows. Ignored on Unix. + * Default: false. */ + windowsVerbatimArguments?: boolean; + + /** + * NOTE: This option is not yet implemented. + */ + windowsHide?: boolean; +} + +function copyProcessEnvToEnv( + env: Record<string, string | number | boolean | undefined>, + name: string, + optionEnv?: Record<string, string | number | boolean>, +) { + if ( + Deno.env.get(name) && + (!optionEnv || + !ObjectPrototypeHasOwnProperty(optionEnv, name)) + ) { + env[name] = Deno.env.get(name); + } +} + +function normalizeStdioOption( + stdio: Array<NodeStdio | number | null | undefined | Stream> | NodeStdio = [ + "pipe", + "pipe", + "pipe", + ], +) { + if (Array.isArray(stdio)) { + return stdio; + } else { + switch (stdio) { + case "overlapped": + if (isWindows) { + notImplemented("normalizeStdioOption overlapped (on windows)"); + } + // 'overlapped' is same as 'piped' on non Windows system. + return ["pipe", "pipe", "pipe"]; + case "pipe": + return ["pipe", "pipe", "pipe"]; + case "inherit": + return ["inherit", "inherit", "inherit"]; + case "ignore": + return ["ignore", "ignore", "ignore"]; + default: + notImplemented(`normalizeStdioOption stdio=${typeof stdio} (${stdio})`); + } + } +} + +export function normalizeSpawnArguments( + file: string, + args: string[], + options: SpawnOptions & SpawnSyncOptions, +) { + validateString(file, "file"); + + if (file.length === 0) { + throw new ERR_INVALID_ARG_VALUE("file", file, "cannot be empty"); + } + + if (ArrayIsArray(args)) { + args = ArrayPrototypeSlice(args); + } else if (args == null) { + args = []; + } else if (typeof args !== "object") { + throw new ERR_INVALID_ARG_TYPE("args", "object", args); + } else { + options = args; + args = []; + } + + if (options === undefined) { + options = kEmptyObject; + } else { + validateObject(options, "options"); + } + + let cwd = options.cwd; + + // Validate the cwd, if present. + if (cwd != null) { + cwd = getValidatedPath(cwd, "options.cwd") as string; + } + + // Validate detached, if present. + if (options.detached != null) { + validateBoolean(options.detached, "options.detached"); + } + + // Validate the uid, if present. + if (options.uid != null && !isInt32(options.uid)) { + throw new ERR_INVALID_ARG_TYPE("options.uid", "int32", options.uid); + } + + // Validate the gid, if present. + if (options.gid != null && !isInt32(options.gid)) { + throw new ERR_INVALID_ARG_TYPE("options.gid", "int32", options.gid); + } + + // Validate the shell, if present. + if ( + options.shell != null && + typeof options.shell !== "boolean" && + typeof options.shell !== "string" + ) { + throw new ERR_INVALID_ARG_TYPE( + "options.shell", + ["boolean", "string"], + options.shell, + ); + } + + // Validate argv0, if present. + if (options.argv0 != null) { + validateString(options.argv0, "options.argv0"); + } + + // Validate windowsHide, if present. + if (options.windowsHide != null) { + validateBoolean(options.windowsHide, "options.windowsHide"); + } + + // Validate windowsVerbatimArguments, if present. + let { windowsVerbatimArguments } = options; + if (windowsVerbatimArguments != null) { + validateBoolean( + windowsVerbatimArguments, + "options.windowsVerbatimArguments", + ); + } + + if (options.shell) { + const command = ArrayPrototypeJoin([file, ...args], " "); + // Set the shell, switches, and commands. + if (process.platform === "win32") { + if (typeof options.shell === "string") { + file = options.shell; + } else { + file = Deno.env.get("comspec") || "cmd.exe"; + } + // '/d /s /c' is used only for cmd.exe. + if (/^(?:.*\\)?cmd(?:\.exe)?$/i.exec(file) !== null) { + args = ["/d", "/s", "/c", `"${command}"`]; + windowsVerbatimArguments = true; + } else { + args = ["-c", command]; + } + } else { + /** TODO: add Android condition */ + if (typeof options.shell === "string") { + file = options.shell; + } else { + file = "/bin/sh"; + } + args = ["-c", command]; + } + } + + if (typeof options.argv0 === "string") { + ArrayPrototypeUnshift(args, options.argv0); + } else { + ArrayPrototypeUnshift(args, file); + } + + const env = options.env || Deno.env.toObject(); + const envPairs: string[][] = []; + + // process.env.NODE_V8_COVERAGE always propagates, making it possible to + // collect coverage for programs that spawn with white-listed environment. + copyProcessEnvToEnv(env, "NODE_V8_COVERAGE", options.env); + + /** TODO: add `isZOS` condition */ + + let envKeys: string[] = []; + // Prototype values are intentionally included. + for (const key in env) { + if (Object.hasOwn(env, key)) { + ArrayPrototypePush(envKeys, key); + } + } + + if (process.platform === "win32") { + // On Windows env keys are case insensitive. Filter out duplicates, + // keeping only the first one (in lexicographic order) + /** TODO: implement SafeSet and makeSafe */ + const sawKey = new Set(); + envKeys = ArrayPrototypeFilter( + ArrayPrototypeSort(envKeys), + (key: string) => { + const uppercaseKey = StringPrototypeToUpperCase(key); + if (sawKey.has(uppercaseKey)) { + return false; + } + sawKey.add(uppercaseKey); + return true; + }, + ); + } + + for (const key of envKeys) { + const value = env[key]; + if (value !== undefined) { + ArrayPrototypePush(envPairs, `${key}=${value}`); + } + } + + return { + // Make a shallow copy so we don't clobber the user's options object. + ...options, + args, + cwd, + detached: !!options.detached, + envPairs, + file, + windowsHide: !!options.windowsHide, + windowsVerbatimArguments: !!windowsVerbatimArguments, + }; +} + +function waitForReadableToClose(readable: Readable) { + readable.resume(); // Ensure buffered data will be consumed. + return waitForStreamToClose(readable as unknown as Stream); +} + +function waitForStreamToClose(stream: Stream) { + const promise = deferred<void>(); + const cleanup = () => { + stream.removeListener("close", onClose); + stream.removeListener("error", onError); + }; + const onClose = () => { + cleanup(); + promise.resolve(); + }; + const onError = (err: Error) => { + cleanup(); + promise.reject(err); + }; + stream.once("close", onClose); + stream.once("error", onError); + return promise; +} + +/** + * This function is based on https://github.com/nodejs/node/blob/fc6426ccc4b4cb73076356fb6dbf46a28953af01/lib/child_process.js#L504-L528. + * Copyright Joyent, Inc. and other Node contributors. All rights reserved. MIT license. + */ +function buildCommand( + file: string, + args: string[], + shell: string | boolean, +): [string, string[]] { + if (file === Deno.execPath()) { + // The user is trying to spawn another Deno process as Node.js. + args = toDenoArgs(args); + } + + if (shell) { + const command = [file, ...args].join(" "); + + // Set the shell, switches, and commands. + if (isWindows) { + if (typeof shell === "string") { + file = shell; + } else { + file = Deno.env.get("comspec") || "cmd.exe"; + } + // '/d /s /c' is used only for cmd.exe. + if (/^(?:.*\\)?cmd(?:\.exe)?$/i.test(file)) { + args = ["/d", "/s", "/c", `"${command}"`]; + } else { + args = ["-c", command]; + } + } else { + if (typeof shell === "string") { + file = shell; + } else { + file = "/bin/sh"; + } + args = ["-c", command]; + } + } + return [file, args]; +} + +function _createSpawnSyncError( + status: string, + command: string, + args: string[] = [], +): ErrnoException { + const error = errnoException( + codeMap.get(status), + "spawnSync " + command, + ); + error.path = command; + error.spawnargs = args; + return error; +} + +export interface SpawnOptions extends ChildProcessOptions { + /** + * NOTE: This option is not yet implemented. + */ + timeout?: number; + /** + * NOTE: This option is not yet implemented. + */ + killSignal?: string; +} + +export interface SpawnSyncOptions extends + Pick< + ChildProcessOptions, + | "cwd" + | "env" + | "argv0" + | "stdio" + | "uid" + | "gid" + | "shell" + | "windowsVerbatimArguments" + | "windowsHide" + > { + input?: string | Buffer | DataView; + timeout?: number; + maxBuffer?: number; + encoding?: string; + /** + * NOTE: This option is not yet implemented. + */ + killSignal?: string; +} + +export interface SpawnSyncResult { + pid?: number; + output?: [string | null, string | Buffer | null, string | Buffer | null]; + stdout?: Buffer | string | null; + stderr?: Buffer | string | null; + status?: number | null; + signal?: string | null; + error?: Error; +} + +function parseSpawnSyncOutputStreams( + output: Deno.CommandOutput, + name: "stdout" | "stderr", +): string | Buffer | null { + // new Deno.Command().outputSync() returns getters for stdout and stderr that throw when set + // to 'inherit'. + try { + return Buffer.from(output[name]) as string | Buffer; + } catch { + return null; + } +} + +export function spawnSync( + command: string, + args: string[], + options: SpawnSyncOptions, +): SpawnSyncResult { + const { + env = Deno.env.toObject(), + stdio = ["pipe", "pipe", "pipe"], + shell = false, + cwd, + encoding, + uid, + gid, + maxBuffer, + windowsVerbatimArguments = false, + } = options; + const normalizedStdio = normalizeStdioOption(stdio); + [command, args] = buildCommand(command, args ?? [], shell); + + const result: SpawnSyncResult = {}; + try { + const output = new DenoCommand(command, { + args, + cwd, + env, + stdout: toDenoStdio(normalizedStdio[1] as NodeStdio | number), + stderr: toDenoStdio(normalizedStdio[2] as NodeStdio | number), + uid, + gid, + windowsRawArguments: windowsVerbatimArguments, + }).outputSync(); + + const status = output.signal ? null : 0; + let stdout = parseSpawnSyncOutputStreams(output, "stdout"); + let stderr = parseSpawnSyncOutputStreams(output, "stderr"); + + if ( + (stdout && stdout.length > maxBuffer!) || + (stderr && stderr.length > maxBuffer!) + ) { + result.error = _createSpawnSyncError("ENOBUFS", command, args); + } + + if (encoding && encoding !== "buffer") { + stdout = stdout && stdout.toString(encoding); + stderr = stderr && stderr.toString(encoding); + } + + result.status = status; + result.signal = output.signal; + result.stdout = stdout; + result.stderr = stderr; + result.output = [output.signal, stdout, stderr]; + } catch (err) { + if (err instanceof Deno.errors.NotFound) { + result.error = _createSpawnSyncError("ENOENT", command, args); + } + } + return result; +} + +// These are Node.js CLI flags that expect a value. It's necessary to +// understand these flags in order to properly replace flags passed to the +// child process. For example, -e is a Node flag for eval mode if it is part +// of process.execArgv. However, -e could also be an application flag if it is +// part of process.execv instead. We only want to process execArgv flags. +const kLongArgType = 1; +const kShortArgType = 2; +const kLongArg = { type: kLongArgType }; +const kShortArg = { type: kShortArgType }; +const kNodeFlagsMap = new Map([ + ["--build-snapshot", kLongArg], + ["-c", kShortArg], + ["--check", kLongArg], + ["-C", kShortArg], + ["--conditions", kLongArg], + ["--cpu-prof-dir", kLongArg], + ["--cpu-prof-interval", kLongArg], + ["--cpu-prof-name", kLongArg], + ["--diagnostic-dir", kLongArg], + ["--disable-proto", kLongArg], + ["--dns-result-order", kLongArg], + ["-e", kShortArg], + ["--eval", kLongArg], + ["--experimental-loader", kLongArg], + ["--experimental-policy", kLongArg], + ["--experimental-specifier-resolution", kLongArg], + ["--heapsnapshot-near-heap-limit", kLongArg], + ["--heapsnapshot-signal", kLongArg], + ["--heap-prof-dir", kLongArg], + ["--heap-prof-interval", kLongArg], + ["--heap-prof-name", kLongArg], + ["--icu-data-dir", kLongArg], + ["--input-type", kLongArg], + ["--inspect-publish-uid", kLongArg], + ["--max-http-header-size", kLongArg], + ["--openssl-config", kLongArg], + ["-p", kShortArg], + ["--print", kLongArg], + ["--policy-integrity", kLongArg], + ["--prof-process", kLongArg], + ["-r", kShortArg], + ["--require", kLongArg], + ["--redirect-warnings", kLongArg], + ["--report-dir", kLongArg], + ["--report-directory", kLongArg], + ["--report-filename", kLongArg], + ["--report-signal", kLongArg], + ["--secure-heap", kLongArg], + ["--secure-heap-min", kLongArg], + ["--snapshot-blob", kLongArg], + ["--title", kLongArg], + ["--tls-cipher-list", kLongArg], + ["--tls-keylog", kLongArg], + ["--unhandled-rejections", kLongArg], + ["--use-largepages", kLongArg], + ["--v8-pool-size", kLongArg], +]); +const kDenoSubcommands = new Set([ + "bench", + "bundle", + "cache", + "check", + "compile", + "completions", + "coverage", + "doc", + "eval", + "fmt", + "help", + "info", + "init", + "install", + "lint", + "lsp", + "repl", + "run", + "tasks", + "test", + "types", + "uninstall", + "upgrade", + "vendor", +]); + +function toDenoArgs(args: string[]): string[] { + if (args.length === 0) { + return args; + } + + // Update this logic as more CLI arguments are mapped from Node to Deno. + const denoArgs: string[] = []; + let useRunArgs = true; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (arg.charAt(0) !== "-" || arg === "--") { + // Not a flag or no more arguments. + + // If the arg is a Deno subcommand, then the child process is being + // spawned as Deno, not Deno in Node compat mode. In this case, bail out + // and return the original args. + if (kDenoSubcommands.has(arg)) { + return args; + } + + // Copy of the rest of the arguments to the output. + for (let j = i; j < args.length; j++) { + denoArgs.push(args[j]); + } + + break; + } + + // Something that looks like a flag was passed. + let flag = arg; + let flagInfo = kNodeFlagsMap.get(arg); + let isLongWithValue = false; + let flagValue; + + if (flagInfo === undefined) { + // If the flag was not found, it's either not a known flag or it's a long + // flag containing an '='. + const splitAt = arg.indexOf("="); + + if (splitAt !== -1) { + flag = arg.slice(0, splitAt); + flagInfo = kNodeFlagsMap.get(flag); + flagValue = arg.slice(splitAt + 1); + isLongWithValue = true; + } + } + + if (flagInfo === undefined) { + // Not a known flag that expects a value. Just copy it to the output. + denoArgs.push(arg); + continue; + } + + // This is a flag with a value. Get the value if we don't already have it. + if (flagValue === undefined) { + i++; + + if (i >= args.length) { + // There was user error. There should be another arg for the value, but + // there isn't one. Just copy the arg to the output. It's not going + // to work anyway. + denoArgs.push(arg); + continue; + } + + flagValue = args[i]; + } + + // Remap Node's eval flags to Deno. + if (flag === "-e" || flag === "--eval") { + denoArgs.push("eval", flagValue); + useRunArgs = false; + } else if (isLongWithValue) { + denoArgs.push(arg); + } else { + denoArgs.push(flag, flagValue); + } + } + + if (useRunArgs) { + // -A is not ideal, but needed to propagate permissions. + // --unstable is needed for Node compat. + denoArgs.unshift("run", "-A", "--unstable"); + } + + return denoArgs; +} + +export default { + ChildProcess, + normalizeSpawnArguments, + stdioStringToArray, + spawnSync, +}; diff --git a/ext/node/polyfills/internal/cli_table.ts b/ext/node/polyfills/internal/cli_table.ts new file mode 100644 index 000000000..68cc6d044 --- /dev/null +++ b/ext/node/polyfills/internal/cli_table.ts @@ -0,0 +1,85 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { getStringWidth } from "internal:deno_node/polyfills/internal/util/inspect.mjs"; + +// The use of Unicode characters below is the only non-comment use of non-ASCII +// Unicode characters in Node.js built-in modules. If they are ever removed or +// rewritten with \u escapes, then a test will need to be (re-)added to Node.js +// core to verify that Unicode characters work in built-ins. +// Refs: https://github.com/nodejs/node/issues/10673 +const tableChars = { + middleMiddle: "â", + rowMiddle: "âŒ", + topRight: "â", + topLeft: "â", + leftMiddle: "â", + topMiddle: "âŹ", + bottomRight: "â", + bottomLeft: "â", + bottomMiddle: "âŽ", + rightMiddle: "â€", + left: "â ", + right: " â", + middle: " â ", +}; + +const renderRow = (row: string[], columnWidths: number[]) => { + let out = tableChars.left; + for (let i = 0; i < row.length; i++) { + const cell = row[i]; + const len = getStringWidth(cell); + const needed = (columnWidths[i] - len) / 2; + // round(needed) + ceil(needed) will always add up to the amount + // of spaces we need while also left justifying the output. + out += " ".repeat(needed) + cell + + " ".repeat(Math.ceil(needed)); + if (i !== row.length - 1) { + out += tableChars.middle; + } + } + out += tableChars.right; + return out; +}; + +const table = (head: string[], columns: string[][]) => { + const rows: string[][] = []; + const columnWidths = head.map((h) => getStringWidth(h)); + const longestColumn = Math.max(...columns.map((a) => a.length)); + + for (let i = 0; i < head.length; i++) { + const column = columns[i]; + for (let j = 0; j < longestColumn; j++) { + if (rows[j] === undefined) { + rows[j] = []; + } + const value = rows[j][i] = Object.hasOwn(column, j) ? column[j] : ""; + const width = columnWidths[i] || 0; + const counted = getStringWidth(value); + columnWidths[i] = Math.max(width, counted); + } + } + + const divider = columnWidths.map((i) => + tableChars.middleMiddle.repeat(i + 2) + ); + + let result = tableChars.topLeft + + divider.join(tableChars.topMiddle) + + tableChars.topRight + "\n" + + renderRow(head, columnWidths) + "\n" + + tableChars.leftMiddle + + divider.join(tableChars.rowMiddle) + + tableChars.rightMiddle + "\n"; + + for (const row of rows) { + result += `${renderRow(row, columnWidths)}\n`; + } + + result += tableChars.bottomLeft + + divider.join(tableChars.bottomMiddle) + + tableChars.bottomRight; + + return result; +}; +export default table; diff --git a/ext/node/polyfills/internal/console/constructor.mjs b/ext/node/polyfills/internal/console/constructor.mjs new file mode 100644 index 000000000..362c97f68 --- /dev/null +++ b/ext/node/polyfills/internal/console/constructor.mjs @@ -0,0 +1,677 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +// Mock trace for now +const trace = () => {}; +import { + ERR_CONSOLE_WRITABLE_STREAM, + ERR_INCOMPATIBLE_OPTION_PAIR, + ERR_INVALID_ARG_VALUE, + isStackOverflowError, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { + validateArray, + validateInteger, + validateObject, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +const previewEntries = (iter, isKeyValue) => { + if (isKeyValue) { + const arr = [...iter]; + if (Array.isArray(arr[0]) && arr[0].length === 2) { + return [[].concat(...arr), true]; + } + return [arr, false]; + } else { + return [...iter]; + } +}; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +const { isBuffer } = Buffer; +import { formatWithOptions, inspect } from "internal:deno_node/polyfills/internal/util/inspect.mjs"; +import { + isMap, + isMapIterator, + isSet, + isSetIterator, + isTypedArray, +} from "internal:deno_node/polyfills/internal/util/types.ts"; +import { + CHAR_LOWERCASE_B as kTraceBegin, + CHAR_LOWERCASE_E as kTraceEnd, + CHAR_LOWERCASE_N as kTraceInstant, + CHAR_UPPERCASE_C as kTraceCount, +} from "internal:deno_node/polyfills/internal/constants.ts"; +import { clearScreenDown, cursorTo } from "internal:deno_node/polyfills/internal/readline/callbacks.mjs"; +import cliTable from "internal:deno_node/polyfills/internal/cli_table.ts"; +const kCounts = Symbol("counts"); + +const kTraceConsoleCategory = "node,node.console"; + +const kSecond = 1000; +const kMinute = 60 * kSecond; +const kHour = 60 * kMinute; +const kMaxGroupIndentation = 1000; + +// Track amount of indentation required via `console.group()`. +const kGroupIndent = Symbol("kGroupIndent"); +const kGroupIndentationWidth = Symbol("kGroupIndentWidth"); +const kFormatForStderr = Symbol("kFormatForStderr"); +const kFormatForStdout = Symbol("kFormatForStdout"); +const kGetInspectOptions = Symbol("kGetInspectOptions"); +const kColorMode = Symbol("kColorMode"); +const kIsConsole = Symbol("kIsConsole"); +const kWriteToConsole = Symbol("kWriteToConsole"); +const kBindProperties = Symbol("kBindProperties"); +const kBindStreamsEager = Symbol("kBindStreamsEager"); +const kBindStreamsLazy = Symbol("kBindStreamsLazy"); +const kUseStdout = Symbol("kUseStdout"); +const kUseStderr = Symbol("kUseStderr"); + +const optionsMap = new WeakMap(); + +function Console(options /* or: stdout, stderr, ignoreErrors = true */) { + // We have to test new.target here to see if this function is called + // with new, because we need to define a custom instanceof to accommodate + // the global console. + if (!new.target) { + return Reflect.construct(Console, arguments); + } + + if (!options || typeof options.write === "function") { + options = { + stdout: options, + stderr: arguments[1], + ignoreErrors: arguments[2], + }; + } + + const { + stdout, + stderr = stdout, + ignoreErrors = true, + colorMode = "auto", + inspectOptions, + groupIndentation, + } = options; + + if (!stdout || typeof stdout.write !== "function") { + throw new ERR_CONSOLE_WRITABLE_STREAM("stdout"); + } + if (!stderr || typeof stderr.write !== "function") { + throw new ERR_CONSOLE_WRITABLE_STREAM("stderr"); + } + + if (typeof colorMode !== "boolean" && colorMode !== "auto") { + throw new ERR_INVALID_ARG_VALUE("colorMode", colorMode); + } + + if (groupIndentation !== undefined) { + validateInteger( + groupIndentation, + "groupIndentation", + 0, + kMaxGroupIndentation, + ); + } + + if (inspectOptions !== undefined) { + validateObject(inspectOptions, "options.inspectOptions"); + + if ( + inspectOptions.colors !== undefined && + options.colorMode !== undefined + ) { + throw new ERR_INCOMPATIBLE_OPTION_PAIR( + "options.inspectOptions.color", + "colorMode", + ); + } + optionsMap.set(this, inspectOptions); + } + + // Bind the prototype functions to this Console instance + Object.keys(Console.prototype).forEach((key) => { + // We have to bind the methods grabbed from the instance instead of from + // the prototype so that users extending the Console can override them + // from the prototype chain of the subclass. + this[key] = this[key].bind(this); + Object.defineProperty(this[key], "name", { + value: key, + }); + }); + + this[kBindStreamsEager](stdout, stderr); + this[kBindProperties](ignoreErrors, colorMode, groupIndentation); +} + +const consolePropAttributes = { + writable: true, + enumerable: false, + configurable: true, +}; + +// Fixup global.console instanceof global.console.Console +Object.defineProperty(Console, Symbol.hasInstance, { + value(instance) { + return instance === console || instance[kIsConsole]; + }, +}); + +const kColorInspectOptions = { colors: true }; +const kNoColorInspectOptions = {}; + +Object.defineProperties(Console.prototype, { + [kBindStreamsEager]: { + ...consolePropAttributes, + // Eager version for the Console constructor + value: function (stdout, stderr) { + Object.defineProperties(this, { + "_stdout": { ...consolePropAttributes, value: stdout }, + "_stderr": { ...consolePropAttributes, value: stderr }, + }); + }, + }, + [kBindStreamsLazy]: { + ...consolePropAttributes, + // Lazily load the stdout and stderr from an object so we don't + // create the stdio streams when they are not even accessed + value: function (object) { + let stdout; + let stderr; + Object.defineProperties(this, { + "_stdout": { + enumerable: false, + configurable: true, + get() { + if (!stdout) stdout = object.stdout; + return stdout; + }, + set(value) { + stdout = value; + }, + }, + "_stderr": { + enumerable: false, + configurable: true, + get() { + if (!stderr) stderr = object.stderr; + return stderr; + }, + set(value) { + stderr = value; + }, + }, + }); + }, + }, + [kBindProperties]: { + ...consolePropAttributes, + value: function (ignoreErrors, colorMode, groupIndentation = 2) { + Object.defineProperties(this, { + "_stdoutErrorHandler": { + ...consolePropAttributes, + value: createWriteErrorHandler(this, kUseStdout), + }, + "_stderrErrorHandler": { + ...consolePropAttributes, + value: createWriteErrorHandler(this, kUseStderr), + }, + "_ignoreErrors": { + ...consolePropAttributes, + value: Boolean(ignoreErrors), + }, + "_times": { ...consolePropAttributes, value: new Map() }, + // Corresponds to https://console.spec.whatwg.org/#count-map + [kCounts]: { ...consolePropAttributes, value: new Map() }, + [kColorMode]: { ...consolePropAttributes, value: colorMode }, + [kIsConsole]: { ...consolePropAttributes, value: true }, + [kGroupIndent]: { ...consolePropAttributes, value: "" }, + [kGroupIndentationWidth]: { + ...consolePropAttributes, + value: groupIndentation, + }, + [Symbol.toStringTag]: { + writable: false, + enumerable: false, + configurable: true, + value: "console", + }, + }); + }, + }, + [kWriteToConsole]: { + ...consolePropAttributes, + value: function (streamSymbol, string) { + const ignoreErrors = this._ignoreErrors; + const groupIndent = this[kGroupIndent]; + + const useStdout = streamSymbol === kUseStdout; + const stream = useStdout ? this._stdout : this._stderr; + const errorHandler = useStdout + ? this._stdoutErrorHandler + : this._stderrErrorHandler; + + if (groupIndent.length !== 0) { + if (string.includes("\n")) { + string = string.replace(/\n/g, `\n${groupIndent}`); + } + string = groupIndent + string; + } + string += "\n"; + + if (ignoreErrors === false) return stream.write(string); + + // There may be an error occurring synchronously (e.g. for files or TTYs + // on POSIX systems) or asynchronously (e.g. pipes on POSIX systems), so + // handle both situations. + try { + // Add and later remove a noop error handler to catch synchronous + // errors. + if (stream.listenerCount("error") === 0) { + stream.once("error", noop); + } + + stream.write(string, errorHandler); + } catch (e) { + // Console is a debugging utility, so it swallowing errors is not + // desirable even in edge cases such as low stack space. + if (isStackOverflowError(e)) { + throw e; + } + // Sorry, there's no proper way to pass along the error here. + } finally { + stream.removeListener("error", noop); + } + }, + }, + [kGetInspectOptions]: { + ...consolePropAttributes, + value: function (stream) { + let color = this[kColorMode]; + if (color === "auto") { + color = stream.isTTY && ( + typeof stream.getColorDepth === "function" + ? stream.getColorDepth() > 2 + : true + ); + } + + const options = optionsMap.get(this); + if (options) { + if (options.colors === undefined) { + options.colors = color; + } + return options; + } + + return color ? kColorInspectOptions : kNoColorInspectOptions; + }, + }, + [kFormatForStdout]: { + ...consolePropAttributes, + value: function (args) { + const opts = this[kGetInspectOptions](this._stdout); + args.unshift(opts); + return Reflect.apply(formatWithOptions, null, args); + }, + }, + [kFormatForStderr]: { + ...consolePropAttributes, + value: function (args) { + const opts = this[kGetInspectOptions](this._stderr); + args.unshift(opts); + return Reflect.apply(formatWithOptions, null, args); + }, + }, +}); + +// Make a function that can serve as the callback passed to `stream.write()`. +function createWriteErrorHandler(instance, streamSymbol) { + return (err) => { + // This conditional evaluates to true if and only if there was an error + // that was not already emitted (which happens when the _write callback + // is invoked asynchronously). + const stream = streamSymbol === kUseStdout + ? instance._stdout + : instance._stderr; + if (err !== null && !stream._writableState.errorEmitted) { + // If there was an error, it will be emitted on `stream` as + // an `error` event. Adding a `once` listener will keep that error + // from becoming an uncaught exception, but since the handler is + // removed after the event, non-console.* writes won't be affected. + // we are only adding noop if there is no one else listening for 'error' + if (stream.listenerCount("error") === 0) { + stream.once("error", noop); + } + } + }; +} + +const consoleMethods = { + log(...args) { + this[kWriteToConsole](kUseStdout, this[kFormatForStdout](args)); + }, + + warn(...args) { + this[kWriteToConsole](kUseStderr, this[kFormatForStderr](args)); + }, + + dir(object, options) { + this[kWriteToConsole]( + kUseStdout, + inspect(object, { + customInspect: false, + ...this[kGetInspectOptions](this._stdout), + ...options, + }), + ); + }, + + time(label = "default") { + // Coerces everything other than Symbol to a string + label = `${label}`; + if (this._times.has(label)) { + emitWarning(`Label '${label}' already exists for console.time()`); + return; + } + trace(kTraceBegin, kTraceConsoleCategory, `time::${label}`, 0); + this._times.set(label, process.hrtime()); + }, + + timeEnd(label = "default") { + // Coerces everything other than Symbol to a string + label = `${label}`; + const found = timeLogImpl(this, "timeEnd", label); + trace(kTraceEnd, kTraceConsoleCategory, `time::${label}`, 0); + if (found) { + this._times.delete(label); + } + }, + + timeLog(label = "default", ...data) { + // Coerces everything other than Symbol to a string + label = `${label}`; + timeLogImpl(this, "timeLog", label, data); + trace(kTraceInstant, kTraceConsoleCategory, `time::${label}`, 0); + }, + + trace: function trace(...args) { + const err = { + name: "Trace", + message: this[kFormatForStderr](args), + }; + Error.captureStackTrace(err, trace); + this.error(err.stack); + }, + + assert(expression, ...args) { + if (!expression) { + args[0] = `Assertion failed${args.length === 0 ? "" : `: ${args[0]}`}`; + // The arguments will be formatted in warn() again + Reflect.apply(this.warn, this, args); + } + }, + + // Defined by: https://console.spec.whatwg.org/#clear + clear() { + // It only makes sense to clear if _stdout is a TTY. + // Otherwise, do nothing. + if (this._stdout.isTTY && process.env.TERM !== "dumb") { + cursorTo(this._stdout, 0, 0); + clearScreenDown(this._stdout); + } + }, + + // Defined by: https://console.spec.whatwg.org/#count + count(label = "default") { + // Ensures that label is a string, and only things that can be + // coerced to strings. e.g. Symbol is not allowed + label = `${label}`; + const counts = this[kCounts]; + let count = counts.get(label); + if (count === undefined) { + count = 1; + } else { + count++; + } + counts.set(label, count); + trace(kTraceCount, kTraceConsoleCategory, `count::${label}`, 0, count); + this.log(`${label}: ${count}`); + }, + + // Defined by: https://console.spec.whatwg.org/#countreset + countReset(label = "default") { + const counts = this[kCounts]; + if (!counts.has(label)) { + emitWarning(`Count for '${label}' does not exist`); + return; + } + trace(kTraceCount, kTraceConsoleCategory, `count::${label}`, 0, 0); + counts.delete(`${label}`); + }, + + group(...data) { + if (data.length > 0) { + Reflect.apply(this.log, this, data); + } + this[kGroupIndent] += " ".repeat(this[kGroupIndentationWidth]); + }, + + groupEnd() { + this[kGroupIndent] = this[kGroupIndent].slice( + 0, + this[kGroupIndent].length - this[kGroupIndentationWidth], + ); + }, + + // https://console.spec.whatwg.org/#table + table(tabularData, properties) { + console.log("tabularData", tabularData); + if (properties !== undefined) { + validateArray(properties, "properties"); + } + + if (tabularData === null || typeof tabularData !== "object") { + return this.log(tabularData); + } + + const final = (k, v) => this.log(cliTable(k, v)); + + const _inspect = (v) => { + const depth = v !== null && + typeof v === "object" && + !isArray(v) && + Object.keys(v).length > 2 + ? -1 + : 0; + const opt = { + depth, + maxArrayLength: 3, + breakLength: Infinity, + ...this[kGetInspectOptions](this._stdout), + }; + return inspect(v, opt); + }; + const getIndexArray = (length) => + Array.from( + { length }, + (_, i) => _inspect(i), + ); + + const mapIter = isMapIterator(tabularData); + let isKeyValue = false; + let i = 0; + if (mapIter) { + const res = previewEntries(tabularData, true); + tabularData = res[0]; + isKeyValue = res[1]; + } + + if (isKeyValue || isMap(tabularData)) { + const keys = []; + const values = []; + let length = 0; + if (mapIter) { + for (; i < tabularData.length / 2; ++i) { + keys.push(_inspect(tabularData[i * 2])); + values.push(_inspect(tabularData[i * 2 + 1])); + length++; + } + } else { + for (const { 0: k, 1: v } of tabularData) { + keys.push(_inspect(k)); + values.push(_inspect(v)); + length++; + } + } + return final([ + iterKey, + keyKey, + valuesKey, + ], [ + getIndexArray(length), + keys, + values, + ]); + } + + const setIter = isSetIterator(tabularData); + if (setIter) { + tabularData = previewEntries(tabularData); + } + + const setlike = setIter || mapIter || isSet(tabularData); + if (setlike) { + const values = []; + let length = 0; + console.log("tabularData", tabularData); + for (const v of tabularData) { + values.push(_inspect(v)); + length++; + } + return final([iterKey, valuesKey], [getIndexArray(length), values]); + } + + const map = Object.create(null); + let hasPrimitives = false; + const valuesKeyArray = []; + const indexKeyArray = Object.keys(tabularData); + + for (; i < indexKeyArray.length; i++) { + const item = tabularData[indexKeyArray[i]]; + const primitive = item === null || + (typeof item !== "function" && typeof item !== "object"); + if (properties === undefined && primitive) { + hasPrimitives = true; + valuesKeyArray[i] = _inspect(item); + } else { + const keys = properties || Object.keys(item); + for (const key of keys) { + if (map[key] === undefined) { + map[key] = []; + } + if ( + (primitive && properties) || + !Object.hasOwn(item, key) + ) { + map[key][i] = ""; + } else { + map[key][i] = _inspect(item[key]); + } + } + } + } + + const keys = Object.keys(map); + const values = Object.values(map); + if (hasPrimitives) { + keys.push(valuesKey); + values.push(valuesKeyArray); + } + keys.unshift(indexKey); + values.unshift(indexKeyArray); + + return final(keys, values); + }, +}; + +// Returns true if label was found +function timeLogImpl(self, name, label, data) { + const time = self._times.get(label); + if (time === undefined) { + emitWarning(`No such label '${label}' for console.${name}()`); + return false; + } + const duration = process.hrtime(time); + const ms = duration[0] * 1000 + duration[1] / 1e6; + + const formatted = formatTime(ms); + + if (data === undefined) { + self.log("%s: %s", label, formatted); + } else { + self.log("%s: %s", label, formatted, ...data); + } + return true; +} + +function pad(value) { + return `${value}`.padStart(2, "0"); +} + +function formatTime(ms) { + let hours = 0; + let minutes = 0; + let seconds = 0; + + if (ms >= kSecond) { + if (ms >= kMinute) { + if (ms >= kHour) { + hours = Math.floor(ms / kHour); + ms = ms % kHour; + } + minutes = Math.floor(ms / kMinute); + ms = ms % kMinute; + } + seconds = ms / kSecond; + } + + if (hours !== 0 || minutes !== 0) { + ({ 0: seconds, 1: ms } = seconds.toFixed(3).split(".")); + const res = hours !== 0 ? `${hours}:${pad(minutes)}` : minutes; + return `${res}:${pad(seconds)}.${ms} (${hours !== 0 ? "h:m" : ""}m:ss.mmm)`; + } + + if (seconds !== 0) { + return `${seconds.toFixed(3)}s`; + } + + return `${Number(ms.toFixed(3))}ms`; +} + +const keyKey = "Key"; +const valuesKey = "Values"; +const indexKey = "(index)"; +const iterKey = "(iteration index)"; + +const isArray = (v) => Array.isArray(v) || isTypedArray(v) || isBuffer(v); + +function noop() {} + +for (const method of Reflect.ownKeys(consoleMethods)) { + Console.prototype[method] = consoleMethods[method]; +} + +Console.prototype.debug = Console.prototype.log; +Console.prototype.info = Console.prototype.log; +Console.prototype.dirxml = Console.prototype.log; +Console.prototype.error = Console.prototype.warn; +Console.prototype.groupCollapsed = Console.prototype.group; + +export { Console, formatTime, kBindProperties, kBindStreamsLazy }; +export default { + Console, + kBindStreamsLazy, + kBindProperties, + formatTime, +}; diff --git a/ext/node/polyfills/internal/constants.ts b/ext/node/polyfills/internal/constants.ts new file mode 100644 index 000000000..5c5cafe8d --- /dev/null +++ b/ext/node/polyfills/internal/constants.ts @@ -0,0 +1,105 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +const { ops } = globalThis.__bootstrap.core; +const isWindows = ops.op_node_build_os() === "windows"; + +// Alphabet chars. +export const CHAR_UPPERCASE_A = 65; /* A */ +export const CHAR_LOWERCASE_A = 97; /* a */ +export const CHAR_UPPERCASE_Z = 90; /* Z */ +export const CHAR_LOWERCASE_Z = 122; /* z */ +export const CHAR_UPPERCASE_C = 67; /* C */ +export const CHAR_LOWERCASE_B = 98; /* b */ +export const CHAR_LOWERCASE_E = 101; /* e */ +export const CHAR_LOWERCASE_N = 110; /* n */ + +// Non-alphabetic chars. +export const CHAR_DOT = 46; /* . */ +export const CHAR_FORWARD_SLASH = 47; /* / */ +export const CHAR_BACKWARD_SLASH = 92; /* \ */ +export const CHAR_VERTICAL_LINE = 124; /* | */ +export const CHAR_COLON = 58; /* = */ +export const CHAR_QUESTION_MARK = 63; /* ? */ +export const CHAR_UNDERSCORE = 95; /* _ */ +export const CHAR_LINE_FEED = 10; /* \n */ +export const CHAR_CARRIAGE_RETURN = 13; /* \r */ +export const CHAR_TAB = 9; /* \t */ +export const CHAR_FORM_FEED = 12; /* \f */ +export const CHAR_EXCLAMATION_MARK = 33; /* ! */ +export const CHAR_HASH = 35; /* # */ +export const CHAR_SPACE = 32; /* */ +export const CHAR_NO_BREAK_SPACE = 160; /* \u00A0 */ +export const CHAR_ZERO_WIDTH_NOBREAK_SPACE = 65279; /* \uFEFF */ +export const CHAR_LEFT_SQUARE_BRACKET = 91; /* [ */ +export const CHAR_RIGHT_SQUARE_BRACKET = 93; /* ] */ +export const CHAR_LEFT_ANGLE_BRACKET = 60; /* < */ +export const CHAR_RIGHT_ANGLE_BRACKET = 62; /* > */ +export const CHAR_LEFT_CURLY_BRACKET = 123; /* { */ +export const CHAR_RIGHT_CURLY_BRACKET = 125; /* } */ +export const CHAR_HYPHEN_MINUS = 45; /* - */ +export const CHAR_PLUS = 43; /* + */ +export const CHAR_DOUBLE_QUOTE = 34; /* " */ +export const CHAR_SINGLE_QUOTE = 39; /* ' */ +export const CHAR_PERCENT = 37; /* % */ +export const CHAR_SEMICOLON = 59; /* ; */ +export const CHAR_CIRCUMFLEX_ACCENT = 94; /* ^ */ +export const CHAR_GRAVE_ACCENT = 96; /* ` */ +export const CHAR_AT = 64; /* @ */ +export const CHAR_AMPERSAND = 38; /* & */ +export const CHAR_EQUAL = 61; /* = */ + +// Digits +export const CHAR_0 = 48; /* 0 */ +export const CHAR_9 = 57; /* 9 */ + +export const EOL = isWindows ? "\r\n" : "\n"; + +export default { + CHAR_UPPERCASE_A, + CHAR_LOWERCASE_A, + CHAR_UPPERCASE_Z, + CHAR_LOWERCASE_Z, + CHAR_UPPERCASE_C, + CHAR_LOWERCASE_B, + CHAR_LOWERCASE_E, + CHAR_LOWERCASE_N, + CHAR_DOT, + CHAR_FORWARD_SLASH, + CHAR_BACKWARD_SLASH, + CHAR_VERTICAL_LINE, + CHAR_COLON, + CHAR_QUESTION_MARK, + CHAR_UNDERSCORE, + CHAR_LINE_FEED, + CHAR_CARRIAGE_RETURN, + CHAR_TAB, + CHAR_FORM_FEED, + CHAR_EXCLAMATION_MARK, + CHAR_HASH, + CHAR_SPACE, + CHAR_NO_BREAK_SPACE, + CHAR_ZERO_WIDTH_NOBREAK_SPACE, + CHAR_LEFT_SQUARE_BRACKET, + CHAR_RIGHT_SQUARE_BRACKET, + CHAR_LEFT_ANGLE_BRACKET, + CHAR_RIGHT_ANGLE_BRACKET, + CHAR_LEFT_CURLY_BRACKET, + CHAR_RIGHT_CURLY_BRACKET, + CHAR_HYPHEN_MINUS, + CHAR_PLUS, + CHAR_DOUBLE_QUOTE, + CHAR_SINGLE_QUOTE, + CHAR_PERCENT, + CHAR_SEMICOLON, + CHAR_CIRCUMFLEX_ACCENT, + CHAR_GRAVE_ACCENT, + CHAR_AT, + CHAR_AMPERSAND, + CHAR_EQUAL, + + CHAR_0, + CHAR_9, + + EOL, +}; diff --git a/ext/node/polyfills/internal/crypto/_hex.ts b/ext/node/polyfills/internal/crypto/_hex.ts new file mode 100644 index 000000000..5cc44aaa8 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/_hex.ts @@ -0,0 +1,19 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// deno-fmt-ignore +const hexTable = new Uint8Array([ + 48, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 97, 98, + 99, 100, 101, 102 +]); + +/** Encodes `src` into `src.length * 2` bytes. */ +export function encode(src: Uint8Array): Uint8Array { + const dst = new Uint8Array(src.length * 2); + for (let i = 0; i < dst.length; i++) { + const v = src[i]; + dst[i * 2] = hexTable[v >> 4]; + dst[i * 2 + 1] = hexTable[v & 0x0f]; + } + return dst; +} diff --git a/ext/node/polyfills/internal/crypto/_keys.ts b/ext/node/polyfills/internal/crypto/_keys.ts new file mode 100644 index 000000000..794582bf1 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/_keys.ts @@ -0,0 +1,16 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { kKeyObject } from "internal:deno_node/polyfills/internal/crypto/constants.ts"; + +export const kKeyType = Symbol("kKeyType"); + +export function isKeyObject(obj: unknown): boolean { + return ( + obj != null && (obj as Record<symbol, unknown>)[kKeyType] !== undefined + ); +} + +export function isCryptoKey(obj: unknown): boolean { + return ( + obj != null && (obj as Record<symbol, unknown>)[kKeyObject] !== undefined + ); +} diff --git a/ext/node/polyfills/internal/crypto/_randomBytes.ts b/ext/node/polyfills/internal/crypto/_randomBytes.ts new file mode 100644 index 000000000..41678fcf1 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/_randomBytes.ts @@ -0,0 +1,70 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; + +export const MAX_RANDOM_VALUES = 65536; +export const MAX_SIZE = 4294967295; + +function generateRandomBytes(size: number) { + if (size > MAX_SIZE) { + throw new RangeError( + `The value of "size" is out of range. It must be >= 0 && <= ${MAX_SIZE}. Received ${size}`, + ); + } + + const bytes = Buffer.allocUnsafe(size); + + //Work around for getRandomValues max generation + if (size > MAX_RANDOM_VALUES) { + for (let generated = 0; generated < size; generated += MAX_RANDOM_VALUES) { + globalThis.crypto.getRandomValues( + bytes.slice(generated, generated + MAX_RANDOM_VALUES), + ); + } + } else { + globalThis.crypto.getRandomValues(bytes); + } + + return bytes; +} + +/** + * @param size Buffer length, must be equal or greater than zero + */ +export default function randomBytes(size: number): Buffer; +export default function randomBytes( + size: number, + cb?: (err: Error | null, buf?: Buffer) => void, +): void; +export default function randomBytes( + size: number, + cb?: (err: Error | null, buf?: Buffer) => void, +): Buffer | void { + if (typeof cb === "function") { + let err: Error | null = null, bytes: Buffer; + try { + bytes = generateRandomBytes(size); + } catch (e) { + //NodeJS nonsense + //If the size is out of range it will throw sync, otherwise throw async + if ( + e instanceof RangeError && + e.message.includes('The value of "size" is out of range') + ) { + throw e; + } else if (e instanceof Error) { + err = e; + } else { + err = new Error("[non-error thrown]"); + } + } + setTimeout(() => { + if (err) { + cb(err); + } else { + cb(null, bytes); + } + }, 0); + } else { + return generateRandomBytes(size); + } +} diff --git a/ext/node/polyfills/internal/crypto/_randomFill.ts b/ext/node/polyfills/internal/crypto/_randomFill.ts new file mode 100644 index 000000000..045072696 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/_randomFill.ts @@ -0,0 +1,84 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import randomBytes, { + MAX_SIZE as kMaxUint32, +} from "internal:deno_node/polyfills/internal/crypto/_randomBytes.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; + +const kBufferMaxLength = 0x7fffffff; + +function assertOffset(offset: number, length: number) { + if (offset > kMaxUint32 || offset < 0) { + throw new TypeError("offset must be a uint32"); + } + + if (offset > kBufferMaxLength || offset > length) { + throw new RangeError("offset out of range"); + } +} + +function assertSize(size: number, offset: number, length: number) { + if (size > kMaxUint32 || size < 0) { + throw new TypeError("size must be a uint32"); + } + + if (size + offset > length || size > kBufferMaxLength) { + throw new RangeError("buffer too small"); + } +} + +export default function randomFill( + buf: Buffer, + cb: (err: Error | null, buf: Buffer) => void, +): void; + +export default function randomFill( + buf: Buffer, + offset: number, + cb: (err: Error | null, buf: Buffer) => void, +): void; + +export default function randomFill( + buf: Buffer, + offset: number, + size: number, + cb: (err: Error | null, buf: Buffer) => void, +): void; + +export default function randomFill( + buf: Buffer, + offset?: number | ((err: Error | null, buf: Buffer) => void), + size?: number | ((err: Error | null, buf: Buffer) => void), + cb?: (err: Error | null, buf: Buffer) => void, +) { + if (typeof offset === "function") { + cb = offset; + offset = 0; + size = buf.length; + } else if (typeof size === "function") { + cb = size; + size = buf.length - Number(offset as number); + } + + assertOffset(offset as number, buf.length); + assertSize(size as number, offset as number, buf.length); + + randomBytes(size as number, (err, bytes) => { + if (err) return cb!(err, buf); + bytes?.copy(buf, offset as number); + cb!(null, buf); + }); +} + +export function randomFillSync(buf: Buffer, offset = 0, size?: number) { + assertOffset(offset, buf.length); + + if (size === undefined) size = buf.length - offset; + + assertSize(size, offset, buf.length); + + const bytes = randomBytes(size); + + bytes.copy(buf, offset); + + return buf; +} diff --git a/ext/node/polyfills/internal/crypto/_randomInt.ts b/ext/node/polyfills/internal/crypto/_randomInt.ts new file mode 100644 index 000000000..637251541 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/_randomInt.ts @@ -0,0 +1,60 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +export default function randomInt(max: number): number; +export default function randomInt(min: number, max: number): number; +export default function randomInt( + max: number, + cb: (err: Error | null, n?: number) => void, +): void; +export default function randomInt( + min: number, + max: number, + cb: (err: Error | null, n?: number) => void, +): void; + +export default function randomInt( + max: number, + min?: ((err: Error | null, n?: number) => void) | number, + cb?: (err: Error | null, n?: number) => void, +): number | void { + if (typeof max === "number" && typeof min === "number") { + [max, min] = [min, max]; + } + if (min === undefined) min = 0; + else if (typeof min === "function") { + cb = min; + min = 0; + } + + if ( + !Number.isSafeInteger(min) || + typeof max === "number" && !Number.isSafeInteger(max) + ) { + throw new Error("max or min is not a Safe Number"); + } + + if (max - min > Math.pow(2, 48)) { + throw new RangeError("max - min should be less than 2^48!"); + } + + if (min >= max) { + throw new Error("Min is bigger than Max!"); + } + + const randomBuffer = new Uint32Array(1); + + globalThis.crypto.getRandomValues(randomBuffer); + + const randomNumber = randomBuffer[0] / (0xffffffff + 1); + + min = Math.ceil(min); + max = Math.floor(max); + + const result = Math.floor(randomNumber * (max - min)) + min; + + if (cb) { + cb(null, result); + return; + } + + return result; +} diff --git a/ext/node/polyfills/internal/crypto/certificate.ts b/ext/node/polyfills/internal/crypto/certificate.ts new file mode 100644 index 000000000..f6fb333a9 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/certificate.ts @@ -0,0 +1,23 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { BinaryLike } from "internal:deno_node/polyfills/internal/crypto/types.ts"; + +export class Certificate { + static Certificate = Certificate; + static exportChallenge(_spkac: BinaryLike, _encoding?: string): Buffer { + notImplemented("crypto.Certificate.exportChallenge"); + } + + static exportPublicKey(_spkac: BinaryLike, _encoding?: string): Buffer { + notImplemented("crypto.Certificate.exportPublicKey"); + } + + static verifySpkac(_spkac: BinaryLike, _encoding?: string): boolean { + notImplemented("crypto.Certificate.verifySpkac"); + } +} + +export default Certificate; diff --git a/ext/node/polyfills/internal/crypto/cipher.ts b/ext/node/polyfills/internal/crypto/cipher.ts new file mode 100644 index 000000000..2778b40fa --- /dev/null +++ b/ext/node/polyfills/internal/crypto/cipher.ts @@ -0,0 +1,292 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { + validateInt32, + validateObject, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import type { TransformOptions } from "internal:deno_node/polyfills/_stream.d.ts"; +import { Transform } from "internal:deno_node/polyfills/_stream.mjs"; +import { KeyObject } from "internal:deno_node/polyfills/internal/crypto/keys.ts"; +import type { BufferEncoding } from "internal:deno_node/polyfills/_global.d.ts"; +import type { + BinaryLike, + Encoding, +} from "internal:deno_node/polyfills/internal/crypto/types.ts"; +import { + privateDecrypt, + privateEncrypt, + publicDecrypt, + publicEncrypt, +} from "internal:deno_node/polyfills/_crypto/crypto_browserify/public_encrypt/mod.js"; + +export { + privateDecrypt, + privateEncrypt, + publicDecrypt, + publicEncrypt, +} from "internal:deno_node/polyfills/_crypto/crypto_browserify/public_encrypt/mod.js"; + +export type CipherCCMTypes = + | "aes-128-ccm" + | "aes-192-ccm" + | "aes-256-ccm" + | "chacha20-poly1305"; +export type CipherGCMTypes = "aes-128-gcm" | "aes-192-gcm" | "aes-256-gcm"; +export type CipherOCBTypes = "aes-128-ocb" | "aes-192-ocb" | "aes-256-ocb"; + +export type CipherKey = BinaryLike | KeyObject; + +export interface CipherCCMOptions extends TransformOptions { + authTagLength: number; +} + +export interface CipherGCMOptions extends TransformOptions { + authTagLength?: number | undefined; +} + +export interface CipherOCBOptions extends TransformOptions { + authTagLength: number; +} + +export interface Cipher extends ReturnType<typeof Transform> { + update(data: BinaryLike): Buffer; + update(data: string, inputEncoding: Encoding): Buffer; + update( + data: ArrayBufferView, + inputEncoding: undefined, + outputEncoding: Encoding, + ): string; + update( + data: string, + inputEncoding: Encoding | undefined, + outputEncoding: Encoding, + ): string; + + final(): Buffer; + final(outputEncoding: BufferEncoding): string; + + setAutoPadding(autoPadding?: boolean): this; +} + +export type Decipher = Cipher; + +export interface CipherCCM extends Cipher { + setAAD( + buffer: ArrayBufferView, + options: { + plaintextLength: number; + }, + ): this; + getAuthTag(): Buffer; +} + +export interface CipherGCM extends Cipher { + setAAD( + buffer: ArrayBufferView, + options?: { + plaintextLength: number; + }, + ): this; + getAuthTag(): Buffer; +} + +export interface CipherOCB extends Cipher { + setAAD( + buffer: ArrayBufferView, + options?: { + plaintextLength: number; + }, + ): this; + getAuthTag(): Buffer; +} + +export interface DecipherCCM extends Decipher { + setAuthTag(buffer: ArrayBufferView): this; + setAAD( + buffer: ArrayBufferView, + options: { + plaintextLength: number; + }, + ): this; +} + +export interface DecipherGCM extends Decipher { + setAuthTag(buffer: ArrayBufferView): this; + setAAD( + buffer: ArrayBufferView, + options?: { + plaintextLength: number; + }, + ): this; +} + +export interface DecipherOCB extends Decipher { + setAuthTag(buffer: ArrayBufferView): this; + setAAD( + buffer: ArrayBufferView, + options?: { + plaintextLength: number; + }, + ): this; +} + +export class Cipheriv extends Transform implements Cipher { + constructor( + _cipher: string, + _key: CipherKey, + _iv: BinaryLike | null, + _options?: TransformOptions, + ) { + super(); + + notImplemented("crypto.Cipheriv"); + } + + final(): Buffer; + final(outputEncoding: BufferEncoding): string; + final(_outputEncoding?: string): Buffer | string { + notImplemented("crypto.Cipheriv.prototype.final"); + } + + getAuthTag(): Buffer { + notImplemented("crypto.Cipheriv.prototype.getAuthTag"); + } + + setAAD( + _buffer: ArrayBufferView, + _options?: { + plaintextLength: number; + }, + ): this { + notImplemented("crypto.Cipheriv.prototype.setAAD"); + } + + setAutoPadding(_autoPadding?: boolean): this { + notImplemented("crypto.Cipheriv.prototype.setAutoPadding"); + } + + update(data: BinaryLike): Buffer; + update(data: string, inputEncoding: Encoding): Buffer; + update( + data: ArrayBufferView, + inputEncoding: undefined, + outputEncoding: Encoding, + ): string; + update( + data: string, + inputEncoding: Encoding | undefined, + outputEncoding: Encoding, + ): string; + update( + _data: string | BinaryLike | ArrayBufferView, + _inputEncoding?: Encoding, + _outputEncoding?: Encoding, + ): Buffer | string { + notImplemented("crypto.Cipheriv.prototype.update"); + } +} + +export class Decipheriv extends Transform implements Cipher { + constructor( + _cipher: string, + _key: CipherKey, + _iv: BinaryLike | null, + _options?: TransformOptions, + ) { + super(); + + notImplemented("crypto.Decipheriv"); + } + + final(): Buffer; + final(outputEncoding: BufferEncoding): string; + final(_outputEncoding?: string): Buffer | string { + notImplemented("crypto.Decipheriv.prototype.final"); + } + + setAAD( + _buffer: ArrayBufferView, + _options?: { + plaintextLength: number; + }, + ): this { + notImplemented("crypto.Decipheriv.prototype.setAAD"); + } + + setAuthTag(_buffer: BinaryLike, _encoding?: string): this { + notImplemented("crypto.Decipheriv.prototype.setAuthTag"); + } + + setAutoPadding(_autoPadding?: boolean): this { + notImplemented("crypto.Decipheriv.prototype.setAutoPadding"); + } + + update(data: BinaryLike): Buffer; + update(data: string, inputEncoding: Encoding): Buffer; + update( + data: ArrayBufferView, + inputEncoding: undefined, + outputEncoding: Encoding, + ): string; + update( + data: string, + inputEncoding: Encoding | undefined, + outputEncoding: Encoding, + ): string; + update( + _data: string | BinaryLike | ArrayBufferView, + _inputEncoding?: Encoding, + _outputEncoding?: Encoding, + ): Buffer | string { + notImplemented("crypto.Decipheriv.prototype.update"); + } +} + +export function getCipherInfo( + nameOrNid: string | number, + options?: { keyLength?: number; ivLength?: number }, +) { + if (typeof nameOrNid !== "string" && typeof nameOrNid !== "number") { + throw new ERR_INVALID_ARG_TYPE( + "nameOrNid", + ["string", "number"], + nameOrNid, + ); + } + + if (typeof nameOrNid === "number") { + validateInt32(nameOrNid, "nameOrNid"); + } + + let keyLength, ivLength; + + if (options !== undefined) { + validateObject(options, "options"); + + ({ keyLength, ivLength } = options); + + if (keyLength !== undefined) { + validateInt32(keyLength, "options.keyLength"); + } + + if (ivLength !== undefined) { + validateInt32(ivLength, "options.ivLength"); + } + } + + notImplemented("crypto.getCipherInfo"); +} + +export default { + privateDecrypt, + privateEncrypt, + publicDecrypt, + publicEncrypt, + Cipheriv, + Decipheriv, + getCipherInfo, +}; diff --git a/ext/node/polyfills/internal/crypto/constants.ts b/ext/node/polyfills/internal/crypto/constants.ts new file mode 100644 index 000000000..d62415360 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/constants.ts @@ -0,0 +1,5 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +export const kHandle = Symbol("kHandle"); +export const kKeyObject = Symbol("kKeyObject"); diff --git a/ext/node/polyfills/internal/crypto/diffiehellman.ts b/ext/node/polyfills/internal/crypto/diffiehellman.ts new file mode 100644 index 000000000..eb903ccb3 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/diffiehellman.ts @@ -0,0 +1,306 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { + isAnyArrayBuffer, + isArrayBufferView, +} from "internal:deno_node/polyfills/internal/util/types.ts"; +import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { + validateInt32, + validateString, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + getDefaultEncoding, + toBuf, +} from "internal:deno_node/polyfills/internal/crypto/util.ts"; +import type { + BinaryLike, + BinaryToTextEncoding, + ECDHKeyFormat, +} from "internal:deno_node/polyfills/internal/crypto/types.ts"; +import { KeyObject } from "internal:deno_node/polyfills/internal/crypto/keys.ts"; +import type { BufferEncoding } from "internal:deno_node/polyfills/_global.d.ts"; + +const DH_GENERATOR = 2; + +export class DiffieHellman { + verifyError!: number; + + constructor( + sizeOrKey: unknown, + keyEncoding?: unknown, + generator?: unknown, + genEncoding?: unknown, + ) { + if ( + typeof sizeOrKey !== "number" && + typeof sizeOrKey !== "string" && + !isArrayBufferView(sizeOrKey) && + !isAnyArrayBuffer(sizeOrKey) + ) { + throw new ERR_INVALID_ARG_TYPE( + "sizeOrKey", + ["number", "string", "ArrayBuffer", "Buffer", "TypedArray", "DataView"], + sizeOrKey, + ); + } + + if (typeof sizeOrKey === "number") { + validateInt32(sizeOrKey, "sizeOrKey"); + } + + if ( + keyEncoding && + !Buffer.isEncoding(keyEncoding as BinaryToTextEncoding) && + keyEncoding !== "buffer" + ) { + genEncoding = generator; + generator = keyEncoding; + keyEncoding = false; + } + + const encoding = getDefaultEncoding(); + keyEncoding = keyEncoding || encoding; + genEncoding = genEncoding || encoding; + + if (typeof sizeOrKey !== "number") { + sizeOrKey = toBuf(sizeOrKey as string, keyEncoding as string); + } + + if (!generator) { + generator = DH_GENERATOR; + } else if (typeof generator === "number") { + validateInt32(generator, "generator"); + } else if (typeof generator === "string") { + generator = toBuf(generator, genEncoding as string); + } else if (!isArrayBufferView(generator) && !isAnyArrayBuffer(generator)) { + throw new ERR_INVALID_ARG_TYPE( + "generator", + ["number", "string", "ArrayBuffer", "Buffer", "TypedArray", "DataView"], + generator, + ); + } + + notImplemented("crypto.DiffieHellman"); + } + + computeSecret(otherPublicKey: ArrayBufferView): Buffer; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + ): Buffer; + computeSecret( + otherPublicKey: ArrayBufferView, + outputEncoding: BinaryToTextEncoding, + ): string; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + outputEncoding: BinaryToTextEncoding, + ): string; + computeSecret( + _otherPublicKey: ArrayBufferView | string, + _inputEncoding?: BinaryToTextEncoding, + _outputEncoding?: BinaryToTextEncoding, + ): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.computeSecret"); + } + + generateKeys(): Buffer; + generateKeys(encoding: BinaryToTextEncoding): string; + generateKeys(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.generateKeys"); + } + + getGenerator(): Buffer; + getGenerator(encoding: BinaryToTextEncoding): string; + getGenerator(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.getGenerator"); + } + + getPrime(): Buffer; + getPrime(encoding: BinaryToTextEncoding): string; + getPrime(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.getPrime"); + } + + getPrivateKey(): Buffer; + getPrivateKey(encoding: BinaryToTextEncoding): string; + getPrivateKey(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.getPrivateKey"); + } + + getPublicKey(): Buffer; + getPublicKey(encoding: BinaryToTextEncoding): string; + getPublicKey(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.getPublicKey"); + } + + setPrivateKey(privateKey: ArrayBufferView): void; + setPrivateKey(privateKey: string, encoding: BufferEncoding): void; + setPrivateKey( + _privateKey: ArrayBufferView | string, + _encoding?: BufferEncoding, + ) { + notImplemented("crypto.DiffieHellman.prototype.setPrivateKey"); + } + + setPublicKey(publicKey: ArrayBufferView): void; + setPublicKey(publicKey: string, encoding: BufferEncoding): void; + setPublicKey( + _publicKey: ArrayBufferView | string, + _encoding?: BufferEncoding, + ) { + notImplemented("crypto.DiffieHellman.prototype.setPublicKey"); + } +} + +export class DiffieHellmanGroup { + verifyError!: number; + + constructor(_name: string) { + notImplemented("crypto.DiffieHellmanGroup"); + } + + computeSecret(otherPublicKey: ArrayBufferView): Buffer; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + ): Buffer; + computeSecret( + otherPublicKey: ArrayBufferView, + outputEncoding: BinaryToTextEncoding, + ): string; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + outputEncoding: BinaryToTextEncoding, + ): string; + computeSecret( + _otherPublicKey: ArrayBufferView | string, + _inputEncoding?: BinaryToTextEncoding, + _outputEncoding?: BinaryToTextEncoding, + ): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.computeSecret"); + } + + generateKeys(): Buffer; + generateKeys(encoding: BinaryToTextEncoding): string; + generateKeys(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.generateKeys"); + } + + getGenerator(): Buffer; + getGenerator(encoding: BinaryToTextEncoding): string; + getGenerator(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.getGenerator"); + } + + getPrime(): Buffer; + getPrime(encoding: BinaryToTextEncoding): string; + getPrime(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.getPrime"); + } + + getPrivateKey(): Buffer; + getPrivateKey(encoding: BinaryToTextEncoding): string; + getPrivateKey(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.getPrivateKey"); + } + + getPublicKey(): Buffer; + getPublicKey(encoding: BinaryToTextEncoding): string; + getPublicKey(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.getPublicKey"); + } +} + +export class ECDH { + constructor(curve: string) { + validateString(curve, "curve"); + + notImplemented("crypto.ECDH"); + } + + static convertKey( + _key: BinaryLike, + _curve: string, + _inputEncoding?: BinaryToTextEncoding, + _outputEncoding?: "latin1" | "hex" | "base64" | "base64url", + _format?: "uncompressed" | "compressed" | "hybrid", + ): Buffer | string { + notImplemented("crypto.ECDH.prototype.convertKey"); + } + + computeSecret(otherPublicKey: ArrayBufferView): Buffer; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + ): Buffer; + computeSecret( + otherPublicKey: ArrayBufferView, + outputEncoding: BinaryToTextEncoding, + ): string; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + outputEncoding: BinaryToTextEncoding, + ): string; + computeSecret( + _otherPublicKey: ArrayBufferView | string, + _inputEncoding?: BinaryToTextEncoding, + _outputEncoding?: BinaryToTextEncoding, + ): Buffer | string { + notImplemented("crypto.ECDH.prototype.computeSecret"); + } + + generateKeys(): Buffer; + generateKeys(encoding: BinaryToTextEncoding, format?: ECDHKeyFormat): string; + generateKeys( + _encoding?: BinaryToTextEncoding, + _format?: ECDHKeyFormat, + ): Buffer | string { + notImplemented("crypto.ECDH.prototype.generateKeys"); + } + + getPrivateKey(): Buffer; + getPrivateKey(encoding: BinaryToTextEncoding): string; + getPrivateKey(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.ECDH.prototype.getPrivateKey"); + } + + getPublicKey(): Buffer; + getPublicKey(encoding: BinaryToTextEncoding, format?: ECDHKeyFormat): string; + getPublicKey( + _encoding?: BinaryToTextEncoding, + _format?: ECDHKeyFormat, + ): Buffer | string { + notImplemented("crypto.ECDH.prototype.getPublicKey"); + } + + setPrivateKey(privateKey: ArrayBufferView): void; + setPrivateKey(privateKey: string, encoding: BinaryToTextEncoding): void; + setPrivateKey( + _privateKey: ArrayBufferView | string, + _encoding?: BinaryToTextEncoding, + ): Buffer | string { + notImplemented("crypto.ECDH.prototype.setPrivateKey"); + } +} + +export function diffieHellman(_options: { + privateKey: KeyObject; + publicKey: KeyObject; +}): Buffer { + notImplemented("crypto.diffieHellman"); +} + +export default { + DiffieHellman, + DiffieHellmanGroup, + ECDH, + diffieHellman, +}; diff --git a/ext/node/polyfills/internal/crypto/hash.ts b/ext/node/polyfills/internal/crypto/hash.ts new file mode 100644 index 000000000..7995e5f8c --- /dev/null +++ b/ext/node/polyfills/internal/crypto/hash.ts @@ -0,0 +1,230 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { + TextDecoder, + TextEncoder, +} from "internal:deno_web/08_text_encoding.js"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { Transform } from "internal:deno_node/polyfills/stream.ts"; +import { encode as encodeToHex } from "internal:deno_node/polyfills/internal/crypto/_hex.ts"; +import { + forgivingBase64Encode as encodeToBase64, + forgivingBase64UrlEncode as encodeToBase64Url, +} from "internal:deno_web/00_infra.js"; +import type { TransformOptions } from "internal:deno_node/polyfills/_stream.d.ts"; +import { validateString } from "internal:deno_node/polyfills/internal/validators.mjs"; +import type { + BinaryToTextEncoding, + Encoding, +} from "internal:deno_node/polyfills/internal/crypto/types.ts"; +import { + KeyObject, + prepareSecretKey, +} from "internal:deno_node/polyfills/internal/crypto/keys.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +const { ops } = globalThis.__bootstrap.core; + +const coerceToBytes = (data: string | BufferSource): Uint8Array => { + if (data instanceof Uint8Array) { + return data; + } else if (typeof data === "string") { + // This assumes UTF-8, which may not be correct. + return new TextEncoder().encode(data); + } else if (ArrayBuffer.isView(data)) { + return new Uint8Array(data.buffer, data.byteOffset, data.byteLength); + } else if (data instanceof ArrayBuffer) { + return new Uint8Array(data); + } else { + throw new TypeError("expected data to be string | BufferSource"); + } +}; + +/** + * The Hash class is a utility for creating hash digests of data. It can be used in one of two ways: + * + * - As a stream that is both readable and writable, where data is written to produce a computed hash digest on the readable side, or + * - Using the hash.update() and hash.digest() methods to produce the computed hash. + * + * The crypto.createHash() method is used to create Hash instances. Hash objects are not to be created directly using the new keyword. + */ +export class Hash extends Transform { + #context: number; + + constructor( + algorithm: string | number, + _opts?: TransformOptions, + ) { + super({ + transform(chunk: string, _encoding: string, callback: () => void) { + ops.op_node_hash_update(context, coerceToBytes(chunk)); + callback(); + }, + flush(callback: () => void) { + this.push(context.digest(undefined)); + callback(); + }, + }); + + if (typeof algorithm === "string") { + this.#context = ops.op_node_create_hash( + algorithm, + ); + } else { + this.#context = algorithm; + } + + const context = this.#context; + } + + copy(): Hash { + return new Hash(ops.op_node_clone_hash(this.#context)); + } + + /** + * Updates the hash content with the given data. + */ + update(data: string | ArrayBuffer, _encoding?: string): this { + let bytes; + if (typeof data === "string") { + data = new TextEncoder().encode(data); + bytes = coerceToBytes(data); + } else { + bytes = coerceToBytes(data); + } + + ops.op_node_hash_update(this.#context, bytes); + + return this; + } + + /** + * Calculates the digest of all of the data. + * + * If encoding is provided a string will be returned; otherwise a Buffer is returned. + * + * Supported encodings are currently 'hex', 'binary', 'base64', 'base64url'. + */ + digest(encoding?: string): Buffer | string { + const digest = this.#context.digest(undefined); + if (encoding === undefined) { + return Buffer.from(digest); + } + + switch (encoding) { + case "hex": + return new TextDecoder().decode(encodeToHex(new Uint8Array(digest))); + case "binary": + return String.fromCharCode(...digest); + case "base64": + return encodeToBase64(digest); + case "base64url": + return encodeToBase64Url(digest); + case "buffer": + return Buffer.from(digest); + default: + return Buffer.from(digest).toString(encoding); + } + } +} + +export function Hmac( + hmac: string, + key: string | ArrayBuffer | KeyObject, + options?: TransformOptions, +): Hmac { + return new HmacImpl(hmac, key, options); +} + +type Hmac = HmacImpl; + +class HmacImpl extends Transform { + #ipad: Uint8Array; + #opad: Uint8Array; + #ZEROES = Buffer.alloc(128); + #algorithm: string; + #hash: Hash; + + constructor( + hmac: string, + key: string | ArrayBuffer | KeyObject, + options?: TransformOptions, + ) { + super({ + transform(chunk: string, encoding: string, callback: () => void) { + // deno-lint-ignore no-explicit-any + self.update(coerceToBytes(chunk), encoding as any); + callback(); + }, + flush(callback: () => void) { + this.push(self.digest()); + callback(); + }, + }); + // deno-lint-ignore no-this-alias + const self = this; + if (key instanceof KeyObject) { + notImplemented("Hmac: KeyObject key is not implemented"); + } + + validateString(hmac, "hmac"); + const u8Key = prepareSecretKey(key, options?.encoding) as Buffer; + + const alg = hmac.toLowerCase(); + this.#hash = new Hash(alg, options); + this.#algorithm = alg; + const blockSize = (alg === "sha512" || alg === "sha384") ? 128 : 64; + const keySize = u8Key.length; + + let bufKey: Buffer; + + if (keySize > blockSize) { + bufKey = this.#hash.update(u8Key).digest() as Buffer; + } else { + bufKey = Buffer.concat([u8Key, this.#ZEROES], blockSize); + } + + this.#ipad = Buffer.allocUnsafe(blockSize); + this.#opad = Buffer.allocUnsafe(blockSize); + + for (let i = 0; i < blockSize; i++) { + this.#ipad[i] = bufKey[i] ^ 0x36; + this.#opad[i] = bufKey[i] ^ 0x5C; + } + + this.#hash = new Hash(alg); + this.#hash.update(this.#ipad); + } + + digest(): Buffer; + digest(encoding: BinaryToTextEncoding): string; + digest(encoding?: BinaryToTextEncoding): Buffer | string { + const result = this.#hash.digest(); + + return new Hash(this.#algorithm).update(this.#opad).update(result).digest( + encoding, + ); + } + + update(data: string | ArrayBuffer, inputEncoding?: Encoding): this { + this.#hash.update(data, inputEncoding); + return this; + } +} + +Hmac.prototype = HmacImpl.prototype; + +/** + * Creates and returns a Hash object that can be used to generate hash digests + * using the given `algorithm`. Optional `options` argument controls stream behavior. + */ +export function createHash(algorithm: string, opts?: TransformOptions) { + return new Hash(algorithm, opts); +} + +export default { + Hash, + Hmac, + createHash, +}; diff --git a/ext/node/polyfills/internal/crypto/hkdf.ts b/ext/node/polyfills/internal/crypto/hkdf.ts new file mode 100644 index 000000000..aebdd9152 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/hkdf.ts @@ -0,0 +1,130 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { + validateFunction, + validateInteger, + validateString, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { + ERR_INVALID_ARG_TYPE, + ERR_OUT_OF_RANGE, + hideStackFrames, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { + toBuf, + validateByteSource, +} from "internal:deno_node/polyfills/internal/crypto/util.ts"; +import { + createSecretKey, + isKeyObject, + KeyObject, +} from "internal:deno_node/polyfills/internal/crypto/keys.ts"; +import type { BinaryLike } from "internal:deno_node/polyfills/internal/crypto/types.ts"; +import { kMaxLength } from "internal:deno_node/polyfills/internal/buffer.mjs"; +import { + isAnyArrayBuffer, + isArrayBufferView, +} from "internal:deno_node/polyfills/internal/util/types.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +const validateParameters = hideStackFrames((hash, key, salt, info, length) => { + key = prepareKey(key); + salt = toBuf(salt); + info = toBuf(info); + + validateString(hash, "digest"); + validateByteSource(salt, "salt"); + validateByteSource(info, "info"); + + validateInteger(length, "length", 0, kMaxLength); + + if (info.byteLength > 1024) { + throw new ERR_OUT_OF_RANGE( + "info", + "must not contain more than 1024 bytes", + info.byteLength, + ); + } + + return { + hash, + key, + salt, + info, + length, + }; +}); + +function prepareKey(key: BinaryLike | KeyObject) { + if (isKeyObject(key)) { + return key; + } + + if (isAnyArrayBuffer(key)) { + return createSecretKey(new Uint8Array(key as unknown as ArrayBufferLike)); + } + + key = toBuf(key as string); + + if (!isArrayBufferView(key)) { + throw new ERR_INVALID_ARG_TYPE( + "ikm", + [ + "string", + "SecretKeyObject", + "ArrayBuffer", + "TypedArray", + "DataView", + "Buffer", + ], + key, + ); + } + + return createSecretKey(key); +} + +export function hkdf( + hash: string, + key: BinaryLike | KeyObject, + salt: BinaryLike, + info: BinaryLike, + length: number, + callback: (err: Error | null, derivedKey: ArrayBuffer) => void, +) { + ({ hash, key, salt, info, length } = validateParameters( + hash, + key, + salt, + info, + length, + )); + + validateFunction(callback, "callback"); + + notImplemented("crypto.hkdf"); +} + +export function hkdfSync( + hash: string, + key: BinaryLike | KeyObject, + salt: BinaryLike, + info: BinaryLike, + length: number, +) { + ({ hash, key, salt, info, length } = validateParameters( + hash, + key, + salt, + info, + length, + )); + + notImplemented("crypto.hkdfSync"); +} + +export default { + hkdf, + hkdfSync, +}; diff --git a/ext/node/polyfills/internal/crypto/keygen.ts b/ext/node/polyfills/internal/crypto/keygen.ts new file mode 100644 index 000000000..1a947b95b --- /dev/null +++ b/ext/node/polyfills/internal/crypto/keygen.ts @@ -0,0 +1,682 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { KeyObject } from "internal:deno_node/polyfills/internal/crypto/keys.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + KeyFormat, + KeyType, +} from "internal:deno_node/polyfills/internal/crypto/types.ts"; + +export function generateKey( + _type: "hmac" | "aes", + _options: { + length: number; + }, + _callback: (err: Error | null, key: KeyObject) => void, +) { + notImplemented("crypto.generateKey"); +} + +export interface BasePrivateKeyEncodingOptions<T extends KeyFormat> { + format: T; + cipher?: string | undefined; + passphrase?: string | undefined; +} + +export interface RSAKeyPairOptions< + PubF extends KeyFormat, + PrivF extends KeyFormat, +> { + /** + * Key size in bits + */ + modulusLength: number; + /** + * Public exponent + * @default 0x10001 + */ + publicExponent?: number | undefined; + publicKeyEncoding: { + type: "pkcs1" | "spki"; + format: PubF; + }; + privateKeyEncoding: BasePrivateKeyEncodingOptions<PrivF> & { + type: "pkcs1" | "pkcs8"; + }; +} + +export interface RSAPSSKeyPairOptions< + PubF extends KeyFormat, + PrivF extends KeyFormat, +> { + /** + * Key size in bits + */ + modulusLength: number; + /** + * Public exponent + * @default 0x10001 + */ + publicExponent?: number | undefined; + /** + * Name of the message digest + */ + hashAlgorithm?: string; + /** + * Name of the message digest used by MGF1 + */ + mgf1HashAlgorithm?: string; + /** + * Minimal salt length in bytes + */ + saltLength?: string; + publicKeyEncoding: { + type: "spki"; + format: PubF; + }; + privateKeyEncoding: BasePrivateKeyEncodingOptions<PrivF> & { + type: "pkcs8"; + }; +} + +export interface DSAKeyPairOptions< + PubF extends KeyFormat, + PrivF extends KeyFormat, +> { + /** + * Key size in bits + */ + modulusLength: number; + /** + * Size of q in bits + */ + divisorLength: number; + publicKeyEncoding: { + type: "spki"; + format: PubF; + }; + privateKeyEncoding: BasePrivateKeyEncodingOptions<PrivF> & { + type: "pkcs8"; + }; +} + +export interface ECKeyPairOptions< + PubF extends KeyFormat, + PrivF extends KeyFormat, +> { + /** + * Name of the curve to use. + */ + namedCurve: string; + publicKeyEncoding: { + type: "pkcs1" | "spki"; + format: PubF; + }; + privateKeyEncoding: BasePrivateKeyEncodingOptions<PrivF> & { + type: "sec1" | "pkcs8"; + }; +} + +export interface ED25519KeyPairOptions< + PubF extends KeyFormat, + PrivF extends KeyFormat, +> { + publicKeyEncoding: { + type: "spki"; + format: PubF; + }; + privateKeyEncoding: BasePrivateKeyEncodingOptions<PrivF> & { + type: "pkcs8"; + }; +} + +export interface ED448KeyPairOptions< + PubF extends KeyFormat, + PrivF extends KeyFormat, +> { + publicKeyEncoding: { + type: "spki"; + format: PubF; + }; + privateKeyEncoding: BasePrivateKeyEncodingOptions<PrivF> & { + type: "pkcs8"; + }; +} + +export interface X25519KeyPairOptions< + PubF extends KeyFormat, + PrivF extends KeyFormat, +> { + publicKeyEncoding: { + type: "spki"; + format: PubF; + }; + privateKeyEncoding: BasePrivateKeyEncodingOptions<PrivF> & { + type: "pkcs8"; + }; +} + +export interface X448KeyPairOptions< + PubF extends KeyFormat, + PrivF extends KeyFormat, +> { + publicKeyEncoding: { + type: "spki"; + format: PubF; + }; + privateKeyEncoding: BasePrivateKeyEncodingOptions<PrivF> & { + type: "pkcs8"; + }; +} + +export interface RSAKeyPairKeyObjectOptions { + /** + * Key size in bits + */ + modulusLength: number; + /** + * Public exponent + * @default 0x10001 + */ + publicExponent?: number | undefined; +} + +export interface RSAPSSKeyPairKeyObjectOptions { + /** + * Key size in bits + */ + modulusLength: number; + /** + * Public exponent + * @default 0x10001 + */ + publicExponent?: number | undefined; + /** + * Name of the message digest + */ + hashAlgorithm?: string; + /** + * Name of the message digest used by MGF1 + */ + mgf1HashAlgorithm?: string; + /** + * Minimal salt length in bytes + */ + saltLength?: string; +} + +export interface DSAKeyPairKeyObjectOptions { + /** + * Key size in bits + */ + modulusLength: number; + /** + * Size of q in bits + */ + divisorLength: number; +} + +// deno-lint-ignore no-empty-interface +export interface ED25519KeyPairKeyObjectOptions {} + +// deno-lint-ignore no-empty-interface +export interface ED448KeyPairKeyObjectOptions {} + +// deno-lint-ignore no-empty-interface +export interface X25519KeyPairKeyObjectOptions {} + +// deno-lint-ignore no-empty-interface +export interface X448KeyPairKeyObjectOptions {} + +export interface ECKeyPairKeyObjectOptions { + /** + * Name of the curve to use + */ + namedCurve: string; +} + +export function generateKeyPair( + type: "rsa", + options: RSAKeyPairOptions<"pem", "pem">, + callback: (err: Error | null, publicKey: string, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "rsa", + options: RSAKeyPairOptions<"pem", "der">, + callback: (err: Error | null, publicKey: string, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "rsa", + options: RSAKeyPairOptions<"der", "pem">, + callback: (err: Error | null, publicKey: Buffer, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "rsa", + options: RSAKeyPairOptions<"der", "der">, + callback: (err: Error | null, publicKey: Buffer, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "rsa", + options: RSAKeyPairKeyObjectOptions, + callback: ( + err: Error | null, + publicKey: KeyObject, + privateKey: KeyObject, + ) => void, +): void; +export function generateKeyPair( + type: "rsa-pss", + options: RSAPSSKeyPairOptions<"pem", "pem">, + callback: (err: Error | null, publicKey: string, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "rsa-pss", + options: RSAPSSKeyPairOptions<"pem", "der">, + callback: (err: Error | null, publicKey: string, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "rsa-pss", + options: RSAPSSKeyPairOptions<"der", "pem">, + callback: (err: Error | null, publicKey: Buffer, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "rsa-pss", + options: RSAPSSKeyPairOptions<"der", "der">, + callback: (err: Error | null, publicKey: Buffer, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "rsa-pss", + options: RSAPSSKeyPairKeyObjectOptions, + callback: ( + err: Error | null, + publicKey: KeyObject, + privateKey: KeyObject, + ) => void, +): void; +export function generateKeyPair( + type: "dsa", + options: DSAKeyPairOptions<"pem", "pem">, + callback: (err: Error | null, publicKey: string, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "dsa", + options: DSAKeyPairOptions<"pem", "der">, + callback: (err: Error | null, publicKey: string, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "dsa", + options: DSAKeyPairOptions<"der", "pem">, + callback: (err: Error | null, publicKey: Buffer, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "dsa", + options: DSAKeyPairOptions<"der", "der">, + callback: (err: Error | null, publicKey: Buffer, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "dsa", + options: DSAKeyPairKeyObjectOptions, + callback: ( + err: Error | null, + publicKey: KeyObject, + privateKey: KeyObject, + ) => void, +): void; +export function generateKeyPair( + type: "ec", + options: ECKeyPairOptions<"pem", "pem">, + callback: (err: Error | null, publicKey: string, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "ec", + options: ECKeyPairOptions<"pem", "der">, + callback: (err: Error | null, publicKey: string, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "ec", + options: ECKeyPairOptions<"der", "pem">, + callback: (err: Error | null, publicKey: Buffer, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "ec", + options: ECKeyPairOptions<"der", "der">, + callback: (err: Error | null, publicKey: Buffer, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "ec", + options: ECKeyPairKeyObjectOptions, + callback: ( + err: Error | null, + publicKey: KeyObject, + privateKey: KeyObject, + ) => void, +): void; +export function generateKeyPair( + type: "ed25519", + options: ED25519KeyPairOptions<"pem", "pem">, + callback: (err: Error | null, publicKey: string, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "ed25519", + options: ED25519KeyPairOptions<"pem", "der">, + callback: (err: Error | null, publicKey: string, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "ed25519", + options: ED25519KeyPairOptions<"der", "pem">, + callback: (err: Error | null, publicKey: Buffer, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "ed25519", + options: ED25519KeyPairOptions<"der", "der">, + callback: (err: Error | null, publicKey: Buffer, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "ed25519", + options: ED25519KeyPairKeyObjectOptions | undefined, + callback: ( + err: Error | null, + publicKey: KeyObject, + privateKey: KeyObject, + ) => void, +): void; +export function generateKeyPair( + type: "ed448", + options: ED448KeyPairOptions<"pem", "pem">, + callback: (err: Error | null, publicKey: string, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "ed448", + options: ED448KeyPairOptions<"pem", "der">, + callback: (err: Error | null, publicKey: string, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "ed448", + options: ED448KeyPairOptions<"der", "pem">, + callback: (err: Error | null, publicKey: Buffer, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "ed448", + options: ED448KeyPairOptions<"der", "der">, + callback: (err: Error | null, publicKey: Buffer, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "ed448", + options: ED448KeyPairKeyObjectOptions | undefined, + callback: ( + err: Error | null, + publicKey: KeyObject, + privateKey: KeyObject, + ) => void, +): void; +export function generateKeyPair( + type: "x25519", + options: X25519KeyPairOptions<"pem", "pem">, + callback: (err: Error | null, publicKey: string, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "x25519", + options: X25519KeyPairOptions<"pem", "der">, + callback: (err: Error | null, publicKey: string, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "x25519", + options: X25519KeyPairOptions<"der", "pem">, + callback: (err: Error | null, publicKey: Buffer, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "x25519", + options: X25519KeyPairOptions<"der", "der">, + callback: (err: Error | null, publicKey: Buffer, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "x25519", + options: X25519KeyPairKeyObjectOptions | undefined, + callback: ( + err: Error | null, + publicKey: KeyObject, + privateKey: KeyObject, + ) => void, +): void; +export function generateKeyPair( + type: "x448", + options: X448KeyPairOptions<"pem", "pem">, + callback: (err: Error | null, publicKey: string, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "x448", + options: X448KeyPairOptions<"pem", "der">, + callback: (err: Error | null, publicKey: string, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "x448", + options: X448KeyPairOptions<"der", "pem">, + callback: (err: Error | null, publicKey: Buffer, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "x448", + options: X448KeyPairOptions<"der", "der">, + callback: (err: Error | null, publicKey: Buffer, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "x448", + options: X448KeyPairKeyObjectOptions | undefined, + callback: ( + err: Error | null, + publicKey: KeyObject, + privateKey: KeyObject, + ) => void, +): void; +export function generateKeyPair( + _type: KeyType, + _options: unknown, + _callback: ( + err: Error | null, + // deno-lint-ignore no-explicit-any + publicKey: any, + // deno-lint-ignore no-explicit-any + privateKey: any, + ) => void, +) { + notImplemented("crypto.generateKeyPair"); +} + +export interface KeyPairKeyObjectResult { + publicKey: KeyObject; + privateKey: KeyObject; +} + +export interface KeyPairSyncResult< + T1 extends string | Buffer, + T2 extends string | Buffer, +> { + publicKey: T1; + privateKey: T2; +} + +export function generateKeyPairSync( + type: "rsa", + options: RSAKeyPairOptions<"pem", "pem">, +): KeyPairSyncResult<string, string>; +export function generateKeyPairSync( + type: "rsa", + options: RSAKeyPairOptions<"pem", "der">, +): KeyPairSyncResult<string, Buffer>; +export function generateKeyPairSync( + type: "rsa", + options: RSAKeyPairOptions<"der", "pem">, +): KeyPairSyncResult<Buffer, string>; +export function generateKeyPairSync( + type: "rsa", + options: RSAKeyPairOptions<"der", "der">, +): KeyPairSyncResult<Buffer, Buffer>; +export function generateKeyPairSync( + type: "rsa", + options: RSAKeyPairKeyObjectOptions, +): KeyPairKeyObjectResult; +export function generateKeyPairSync( + type: "rsa-pss", + options: RSAPSSKeyPairOptions<"pem", "pem">, +): KeyPairSyncResult<string, string>; +export function generateKeyPairSync( + type: "rsa-pss", + options: RSAPSSKeyPairOptions<"pem", "der">, +): KeyPairSyncResult<string, Buffer>; +export function generateKeyPairSync( + type: "rsa-pss", + options: RSAPSSKeyPairOptions<"der", "pem">, +): KeyPairSyncResult<Buffer, string>; +export function generateKeyPairSync( + type: "rsa-pss", + options: RSAPSSKeyPairOptions<"der", "der">, +): KeyPairSyncResult<Buffer, Buffer>; +export function generateKeyPairSync( + type: "rsa-pss", + options: RSAPSSKeyPairKeyObjectOptions, +): KeyPairKeyObjectResult; +export function generateKeyPairSync( + type: "dsa", + options: DSAKeyPairOptions<"pem", "pem">, +): KeyPairSyncResult<string, string>; +export function generateKeyPairSync( + type: "dsa", + options: DSAKeyPairOptions<"pem", "der">, +): KeyPairSyncResult<string, Buffer>; +export function generateKeyPairSync( + type: "dsa", + options: DSAKeyPairOptions<"der", "pem">, +): KeyPairSyncResult<Buffer, string>; +export function generateKeyPairSync( + type: "dsa", + options: DSAKeyPairOptions<"der", "der">, +): KeyPairSyncResult<Buffer, Buffer>; +export function generateKeyPairSync( + type: "dsa", + options: DSAKeyPairKeyObjectOptions, +): KeyPairKeyObjectResult; +export function generateKeyPairSync( + type: "ec", + options: ECKeyPairOptions<"pem", "pem">, +): KeyPairSyncResult<string, string>; +export function generateKeyPairSync( + type: "ec", + options: ECKeyPairOptions<"pem", "der">, +): KeyPairSyncResult<string, Buffer>; +export function generateKeyPairSync( + type: "ec", + options: ECKeyPairOptions<"der", "pem">, +): KeyPairSyncResult<Buffer, string>; +export function generateKeyPairSync( + type: "ec", + options: ECKeyPairOptions<"der", "der">, +): KeyPairSyncResult<Buffer, Buffer>; +export function generateKeyPairSync( + type: "ec", + options: ECKeyPairKeyObjectOptions, +): KeyPairKeyObjectResult; +export function generateKeyPairSync( + type: "ed25519", + options: ED25519KeyPairOptions<"pem", "pem">, +): KeyPairSyncResult<string, string>; +export function generateKeyPairSync( + type: "ed25519", + options: ED25519KeyPairOptions<"pem", "der">, +): KeyPairSyncResult<string, Buffer>; +export function generateKeyPairSync( + type: "ed25519", + options: ED25519KeyPairOptions<"der", "pem">, +): KeyPairSyncResult<Buffer, string>; +export function generateKeyPairSync( + type: "ed25519", + options: ED25519KeyPairOptions<"der", "der">, +): KeyPairSyncResult<Buffer, Buffer>; +export function generateKeyPairSync( + type: "ed25519", + options?: ED25519KeyPairKeyObjectOptions, +): KeyPairKeyObjectResult; +export function generateKeyPairSync( + type: "ed448", + options: ED448KeyPairOptions<"pem", "pem">, +): KeyPairSyncResult<string, string>; +export function generateKeyPairSync( + type: "ed448", + options: ED448KeyPairOptions<"pem", "der">, +): KeyPairSyncResult<string, Buffer>; +export function generateKeyPairSync( + type: "ed448", + options: ED448KeyPairOptions<"der", "pem">, +): KeyPairSyncResult<Buffer, string>; +export function generateKeyPairSync( + type: "ed448", + options: ED448KeyPairOptions<"der", "der">, +): KeyPairSyncResult<Buffer, Buffer>; +export function generateKeyPairSync( + type: "ed448", + options?: ED448KeyPairKeyObjectOptions, +): KeyPairKeyObjectResult; +export function generateKeyPairSync( + type: "x25519", + options: X25519KeyPairOptions<"pem", "pem">, +): KeyPairSyncResult<string, string>; +export function generateKeyPairSync( + type: "x25519", + options: X25519KeyPairOptions<"pem", "der">, +): KeyPairSyncResult<string, Buffer>; +export function generateKeyPairSync( + type: "x25519", + options: X25519KeyPairOptions<"der", "pem">, +): KeyPairSyncResult<Buffer, string>; +export function generateKeyPairSync( + type: "x25519", + options: X25519KeyPairOptions<"der", "der">, +): KeyPairSyncResult<Buffer, Buffer>; +export function generateKeyPairSync( + type: "x25519", + options?: X25519KeyPairKeyObjectOptions, +): KeyPairKeyObjectResult; +export function generateKeyPairSync( + type: "x448", + options: X448KeyPairOptions<"pem", "pem">, +): KeyPairSyncResult<string, string>; +export function generateKeyPairSync( + type: "x448", + options: X448KeyPairOptions<"pem", "der">, +): KeyPairSyncResult<string, Buffer>; +export function generateKeyPairSync( + type: "x448", + options: X448KeyPairOptions<"der", "pem">, +): KeyPairSyncResult<Buffer, string>; +export function generateKeyPairSync( + type: "x448", + options: X448KeyPairOptions<"der", "der">, +): KeyPairSyncResult<Buffer, Buffer>; +export function generateKeyPairSync( + type: "x448", + options?: X448KeyPairKeyObjectOptions, +): KeyPairKeyObjectResult; +export function generateKeyPairSync( + _type: KeyType, + _options: unknown, +): + | KeyPairKeyObjectResult + | KeyPairSyncResult<string | Buffer, string | Buffer> { + notImplemented("crypto.generateKeyPairSync"); +} + +export function generateKeySync( + _type: "hmac" | "aes", + _options: { + length: number; + }, +): KeyObject { + notImplemented("crypto.generateKeySync"); +} + +export default { + generateKey, + generateKeySync, + generateKeyPair, + generateKeyPairSync, +}; diff --git a/ext/node/polyfills/internal/crypto/keys.ts b/ext/node/polyfills/internal/crypto/keys.ts new file mode 100644 index 000000000..7c9e7bad9 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/keys.ts @@ -0,0 +1,294 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { + kHandle, + kKeyObject, +} from "internal:deno_node/polyfills/internal/crypto/constants.ts"; +import { + ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import type { + KeyFormat, + KeyType, + PrivateKeyInput, + PublicKeyInput, +} from "internal:deno_node/polyfills/internal/crypto/types.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + isAnyArrayBuffer, + isArrayBufferView, +} from "internal:deno_node/polyfills/internal/util/types.ts"; +import { hideStackFrames } from "internal:deno_node/polyfills/internal/errors.ts"; +import { + isCryptoKey as isCryptoKey_, + isKeyObject as isKeyObject_, + kKeyType, +} from "internal:deno_node/polyfills/internal/crypto/_keys.ts"; + +const getArrayBufferOrView = hideStackFrames( + ( + buffer, + name, + encoding, + ): + | ArrayBuffer + | SharedArrayBuffer + | Buffer + | DataView + | BigInt64Array + | BigUint64Array + | Float32Array + | Float64Array + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint8ClampedArray + | Uint16Array + | Uint32Array => { + if (isAnyArrayBuffer(buffer)) { + return buffer; + } + if (typeof buffer === "string") { + if (encoding === "buffer") { + encoding = "utf8"; + } + return Buffer.from(buffer, encoding); + } + if (!isArrayBufferView(buffer)) { + throw new ERR_INVALID_ARG_TYPE( + name, + [ + "string", + "ArrayBuffer", + "Buffer", + "TypedArray", + "DataView", + ], + buffer, + ); + } + return buffer; + }, +); + +export interface AsymmetricKeyDetails { + /** + * Key size in bits (RSA, DSA). + */ + modulusLength?: number | undefined; + /** + * Public exponent (RSA). + */ + publicExponent?: bigint | undefined; + /** + * Name of the message digest (RSA-PSS). + */ + hashAlgorithm?: string | undefined; + /** + * Name of the message digest used by MGF1 (RSA-PSS). + */ + mgf1HashAlgorithm?: string | undefined; + /** + * Minimal salt length in bytes (RSA-PSS). + */ + saltLength?: number | undefined; + /** + * Size of q in bits (DSA). + */ + divisorLength?: number | undefined; + /** + * Name of the curve (EC). + */ + namedCurve?: string | undefined; +} + +export type KeyObjectType = "secret" | "public" | "private"; + +export interface KeyExportOptions<T extends KeyFormat> { + type: "pkcs1" | "spki" | "pkcs8" | "sec1"; + format: T; + cipher?: string | undefined; + passphrase?: string | Buffer | undefined; +} + +export interface JwkKeyExportOptions { + format: "jwk"; +} + +export function isKeyObject(obj: unknown): obj is KeyObject { + return isKeyObject_(obj); +} + +export function isCryptoKey( + obj: unknown, +): obj is { type: string; [kKeyObject]: KeyObject } { + return isCryptoKey_(obj); +} + +export class KeyObject { + [kKeyType]: KeyObjectType; + [kHandle]: unknown; + + constructor(type: KeyObjectType, handle: unknown) { + if (type !== "secret" && type !== "public" && type !== "private") { + throw new ERR_INVALID_ARG_VALUE("type", type); + } + + if (typeof handle !== "object") { + throw new ERR_INVALID_ARG_TYPE("handle", "object", handle); + } + + this[kKeyType] = type; + + Object.defineProperty(this, kHandle, { + value: handle, + enumerable: false, + configurable: false, + writable: false, + }); + } + + get type(): KeyObjectType { + return this[kKeyType]; + } + + get asymmetricKeyDetails(): AsymmetricKeyDetails | undefined { + notImplemented("crypto.KeyObject.prototype.asymmetricKeyDetails"); + + return undefined; + } + + get asymmetricKeyType(): KeyType | undefined { + notImplemented("crypto.KeyObject.prototype.asymmetricKeyType"); + + return undefined; + } + + get symmetricKeySize(): number | undefined { + notImplemented("crypto.KeyObject.prototype.symmetricKeySize"); + + return undefined; + } + + static from(key: CryptoKey): KeyObject { + if (!isCryptoKey(key)) { + throw new ERR_INVALID_ARG_TYPE("key", "CryptoKey", key); + } + + notImplemented("crypto.KeyObject.prototype.from"); + } + + equals(otherKeyObject: KeyObject): boolean { + if (!isKeyObject(otherKeyObject)) { + throw new ERR_INVALID_ARG_TYPE( + "otherKeyObject", + "KeyObject", + otherKeyObject, + ); + } + + notImplemented("crypto.KeyObject.prototype.equals"); + } + + export(options: KeyExportOptions<"pem">): string | Buffer; + export(options?: KeyExportOptions<"der">): Buffer; + export(options?: JwkKeyExportOptions): JsonWebKey; + export(_options?: unknown): string | Buffer | JsonWebKey { + notImplemented("crypto.KeyObject.prototype.asymmetricKeyType"); + } +} + +export interface JsonWebKeyInput { + key: JsonWebKey; + format: "jwk"; +} + +export function createPrivateKey( + _key: PrivateKeyInput | string | Buffer | JsonWebKeyInput, +): KeyObject { + notImplemented("crypto.createPrivateKey"); +} + +export function createPublicKey( + _key: PublicKeyInput | string | Buffer | KeyObject | JsonWebKeyInput, +): KeyObject { + notImplemented("crypto.createPublicKey"); +} + +function getKeyTypes(allowKeyObject: boolean, bufferOnly = false) { + const types = [ + "ArrayBuffer", + "Buffer", + "TypedArray", + "DataView", + "string", // Only if bufferOnly == false + "KeyObject", // Only if allowKeyObject == true && bufferOnly == false + "CryptoKey", // Only if allowKeyObject == true && bufferOnly == false + ]; + if (bufferOnly) { + return types.slice(0, 4); + } else if (!allowKeyObject) { + return types.slice(0, 5); + } + return types; +} + +export function prepareSecretKey( + key: string | ArrayBuffer | KeyObject, + encoding: string | undefined, + bufferOnly = false, +) { + if (!bufferOnly) { + if (isKeyObject(key)) { + if (key.type !== "secret") { + throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, "secret"); + } + return key[kHandle]; + } else if (isCryptoKey(key)) { + if (key.type !== "secret") { + throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, "secret"); + } + return key[kKeyObject][kHandle]; + } + } + if ( + typeof key !== "string" && + !isArrayBufferView(key) && + !isAnyArrayBuffer(key) + ) { + throw new ERR_INVALID_ARG_TYPE( + "key", + getKeyTypes(!bufferOnly, bufferOnly), + key, + ); + } + + return getArrayBufferOrView(key, "key", encoding); +} + +export function createSecretKey(key: ArrayBufferView): KeyObject; +export function createSecretKey( + key: string, + encoding: string, +): KeyObject; +export function createSecretKey( + _key: string | ArrayBufferView, + _encoding?: string, +): KeyObject { + notImplemented("crypto.createSecretKey"); +} + +export default { + createPrivateKey, + createPublicKey, + createSecretKey, + isKeyObject, + isCryptoKey, + KeyObject, + prepareSecretKey, +}; diff --git a/ext/node/polyfills/internal/crypto/pbkdf2.ts b/ext/node/polyfills/internal/crypto/pbkdf2.ts new file mode 100644 index 000000000..a3d821ae7 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/pbkdf2.ts @@ -0,0 +1,183 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { createHash } from "internal:deno_node/polyfills/internal/crypto/hash.ts"; +import { HASH_DATA } from "internal:deno_node/polyfills/internal/crypto/types.ts"; + +export const MAX_ALLOC = Math.pow(2, 30) - 1; + +export type NormalizedAlgorithms = + | "md5" + | "ripemd160" + | "sha1" + | "sha224" + | "sha256" + | "sha384" + | "sha512"; + +export type Algorithms = + | "md5" + | "ripemd160" + | "rmd160" + | "sha1" + | "sha224" + | "sha256" + | "sha384" + | "sha512"; + +const createHasher = (algorithm: string) => (value: Uint8Array) => + Buffer.from(createHash(algorithm).update(value).digest() as Buffer); + +function getZeroes(zeros: number) { + return Buffer.alloc(zeros); +} + +const sizes = { + md5: 16, + sha1: 20, + sha224: 28, + sha256: 32, + sha384: 48, + sha512: 64, + rmd160: 20, + ripemd160: 20, +}; + +function toBuffer(bufferable: HASH_DATA) { + if (bufferable instanceof Uint8Array || typeof bufferable === "string") { + return Buffer.from(bufferable as Uint8Array); + } else { + return Buffer.from(bufferable.buffer); + } +} + +export class Hmac { + hash: (value: Uint8Array) => Buffer; + ipad1: Buffer; + opad: Buffer; + alg: string; + blocksize: number; + size: number; + ipad2: Buffer; + + constructor(alg: Algorithms, key: Buffer, saltLen: number) { + this.hash = createHasher(alg); + + const blocksize = alg === "sha512" || alg === "sha384" ? 128 : 64; + + if (key.length > blocksize) { + key = this.hash(key); + } else if (key.length < blocksize) { + key = Buffer.concat([key, getZeroes(blocksize - key.length)], blocksize); + } + + const ipad = Buffer.allocUnsafe(blocksize + sizes[alg]); + const opad = Buffer.allocUnsafe(blocksize + sizes[alg]); + for (let i = 0; i < blocksize; i++) { + ipad[i] = key[i] ^ 0x36; + opad[i] = key[i] ^ 0x5c; + } + + const ipad1 = Buffer.allocUnsafe(blocksize + saltLen + 4); + ipad.copy(ipad1, 0, 0, blocksize); + + this.ipad1 = ipad1; + this.ipad2 = ipad; + this.opad = opad; + this.alg = alg; + this.blocksize = blocksize; + this.size = sizes[alg]; + } + + run(data: Buffer, ipad: Buffer) { + data.copy(ipad, this.blocksize); + const h = this.hash(ipad); + h.copy(this.opad, this.blocksize); + return this.hash(this.opad); + } +} + +/** + * @param iterations Needs to be higher or equal than zero + * @param keylen Needs to be higher or equal than zero but less than max allocation size (2^30) + * @param digest Algorithm to be used for encryption + */ +export function pbkdf2Sync( + password: HASH_DATA, + salt: HASH_DATA, + iterations: number, + keylen: number, + digest: Algorithms = "sha1", +): Buffer { + if (typeof iterations !== "number" || iterations < 0) { + throw new TypeError("Bad iterations"); + } + if (typeof keylen !== "number" || keylen < 0 || keylen > MAX_ALLOC) { + throw new TypeError("Bad key length"); + } + + const bufferedPassword = toBuffer(password); + const bufferedSalt = toBuffer(salt); + + const hmac = new Hmac(digest, bufferedPassword, bufferedSalt.length); + + const DK = Buffer.allocUnsafe(keylen); + const block1 = Buffer.allocUnsafe(bufferedSalt.length + 4); + bufferedSalt.copy(block1, 0, 0, bufferedSalt.length); + + let destPos = 0; + const hLen = sizes[digest]; + const l = Math.ceil(keylen / hLen); + + for (let i = 1; i <= l; i++) { + block1.writeUInt32BE(i, bufferedSalt.length); + + const T = hmac.run(block1, hmac.ipad1); + let U = T; + + for (let j = 1; j < iterations; j++) { + U = hmac.run(U, hmac.ipad2); + for (let k = 0; k < hLen; k++) T[k] ^= U[k]; + } + + T.copy(DK, destPos); + destPos += hLen; + } + + return DK; +} + +/** + * @param iterations Needs to be higher or equal than zero + * @param keylen Needs to be higher or equal than zero but less than max allocation size (2^30) + * @param digest Algorithm to be used for encryption + */ +export function pbkdf2( + password: HASH_DATA, + salt: HASH_DATA, + iterations: number, + keylen: number, + digest: Algorithms = "sha1", + callback: (err: Error | null, derivedKey?: Buffer) => void, +) { + setTimeout(() => { + let err = null, + res; + try { + res = pbkdf2Sync(password, salt, iterations, keylen, digest); + } catch (e) { + err = e; + } + if (err) { + callback(err instanceof Error ? err : new Error("[non-error thrown]")); + } else { + callback(null, res); + } + }, 0); +} + +export default { + Hmac, + MAX_ALLOC, + pbkdf2, + pbkdf2Sync, +}; diff --git a/ext/node/polyfills/internal/crypto/random.ts b/ext/node/polyfills/internal/crypto/random.ts new file mode 100644 index 000000000..158ea40da --- /dev/null +++ b/ext/node/polyfills/internal/crypto/random.ts @@ -0,0 +1,140 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import randomBytes from "internal:deno_node/polyfills/internal/crypto/_randomBytes.ts"; +import randomFill, { + randomFillSync, +} from "internal:deno_node/polyfills/internal/crypto/_randomFill.ts"; +import randomInt from "internal:deno_node/polyfills/internal/crypto/_randomInt.ts"; + +export { default as randomBytes } from "internal:deno_node/polyfills/internal/crypto/_randomBytes.ts"; +export { + default as randomFill, + randomFillSync, +} from "internal:deno_node/polyfills/internal/crypto/_randomFill.ts"; +export { default as randomInt } from "internal:deno_node/polyfills/internal/crypto/_randomInt.ts"; + +export type LargeNumberLike = + | ArrayBufferView + | SharedArrayBuffer + | ArrayBuffer + | bigint; + +export interface CheckPrimeOptions { + /** + * The number of Miller-Rabin probabilistic primality iterations to perform. + * When the value is 0 (zero), a number of checks is used that yields a false positive rate of at most 2-64 for random input. + * Care must be used when selecting a number of checks. + * Refer to the OpenSSL documentation for the BN_is_prime_ex function nchecks options for more details. + * + * @default 0 + */ + checks?: number | undefined; +} + +export function checkPrime( + candidate: LargeNumberLike, + callback: (err: Error | null, result: boolean) => void, +): void; +export function checkPrime( + candidate: LargeNumberLike, + options: CheckPrimeOptions, + callback: (err: Error | null, result: boolean) => void, +): void; +export function checkPrime( + _candidate: LargeNumberLike, + _options?: CheckPrimeOptions | ((err: Error | null, result: boolean) => void), + _callback?: (err: Error | null, result: boolean) => void, +) { + notImplemented("crypto.checkPrime"); +} + +export function checkPrimeSync( + _candidate: LargeNumberLike, + _options?: CheckPrimeOptions, +): boolean { + notImplemented("crypto.checkPrimeSync"); +} + +export interface GeneratePrimeOptions { + add?: LargeNumberLike | undefined; + rem?: LargeNumberLike | undefined; + /** + * @default false + */ + safe?: boolean | undefined; + bigint?: boolean | undefined; +} + +export interface GeneratePrimeOptionsBigInt extends GeneratePrimeOptions { + bigint: true; +} + +export interface GeneratePrimeOptionsArrayBuffer extends GeneratePrimeOptions { + bigint?: false | undefined; +} + +export function generatePrime( + size: number, + callback: (err: Error | null, prime: ArrayBuffer) => void, +): void; +export function generatePrime( + size: number, + options: GeneratePrimeOptionsBigInt, + callback: (err: Error | null, prime: bigint) => void, +): void; +export function generatePrime( + size: number, + options: GeneratePrimeOptionsArrayBuffer, + callback: (err: Error | null, prime: ArrayBuffer) => void, +): void; +export function generatePrime( + size: number, + options: GeneratePrimeOptions, + callback: (err: Error | null, prime: ArrayBuffer | bigint) => void, +): void; +export function generatePrime( + _size: number, + _options?: unknown, + _callback?: unknown, +) { + notImplemented("crypto.generatePrime"); +} + +export function generatePrimeSync(size: number): ArrayBuffer; +export function generatePrimeSync( + size: number, + options: GeneratePrimeOptionsBigInt, +): bigint; +export function generatePrimeSync( + size: number, + options: GeneratePrimeOptionsArrayBuffer, +): ArrayBuffer; +export function generatePrimeSync( + size: number, + options: GeneratePrimeOptions, +): ArrayBuffer | bigint; +export function generatePrimeSync( + _size: number, + _options?: + | GeneratePrimeOptionsBigInt + | GeneratePrimeOptionsArrayBuffer + | GeneratePrimeOptions, +): ArrayBuffer | bigint { + notImplemented("crypto.generatePrimeSync"); +} + +export const randomUUID = () => globalThis.crypto.randomUUID(); + +export default { + checkPrime, + checkPrimeSync, + generatePrime, + generatePrimeSync, + randomUUID, + randomInt, + randomBytes, + randomFill, + randomFillSync, +}; diff --git a/ext/node/polyfills/internal/crypto/scrypt.ts b/ext/node/polyfills/internal/crypto/scrypt.ts new file mode 100644 index 000000000..1bdafb63d --- /dev/null +++ b/ext/node/polyfills/internal/crypto/scrypt.ts @@ -0,0 +1,278 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +/* +MIT License + +Copyright (c) 2018 cryptocoinjs + +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 { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { pbkdf2Sync as pbkdf2 } from "internal:deno_node/polyfills/internal/crypto/pbkdf2.ts"; +import { HASH_DATA } from "internal:deno_node/polyfills/internal/crypto/types.ts"; + +type Opts = Partial<{ + N: number; + cost: number; + p: number; + parallelization: number; + r: number; + blockSize: number; + maxmem: number; +}>; + +const fixOpts = (opts?: Opts) => { + const out = { N: 16384, p: 1, r: 8, maxmem: 32 << 20 }; + if (!opts) return out; + + if (opts.N) out.N = opts.N; + else if (opts.cost) out.N = opts.cost; + + if (opts.p) out.p = opts.p; + else if (opts.parallelization) out.p = opts.parallelization; + + if (opts.r) out.r = opts.r; + else if (opts.blockSize) out.r = opts.blockSize; + + if (opts.maxmem) out.maxmem = opts.maxmem; + + return out; +}; + +function blockxor(S: Buffer, Si: number, D: Buffer, Di: number, len: number) { + let i = -1; + while (++i < len) D[Di + i] ^= S[Si + i]; +} +function arraycopy( + src: Buffer, + srcPos: number, + dest: Buffer, + destPos: number, + length: number, +) { + src.copy(dest, destPos, srcPos, srcPos + length); +} + +const R = (a: number, b: number) => (a << b) | (a >>> (32 - b)); + +class ScryptRom { + B: Buffer; + r: number; + N: number; + p: number; + XY: Buffer; + V: Buffer; + B32: Int32Array; + x: Int32Array; + _X: Buffer; + constructor(b: Buffer, r: number, N: number, p: number) { + this.B = b; + this.r = r; + this.N = N; + this.p = p; + this.XY = Buffer.allocUnsafe(256 * r); + this.V = Buffer.allocUnsafe(128 * r * N); + this.B32 = new Int32Array(16); // salsa20_8 + this.x = new Int32Array(16); // salsa20_8 + this._X = Buffer.allocUnsafe(64); // blockmix_salsa8 + } + + run() { + const p = this.p | 0; + const r = this.r | 0; + for (let i = 0; i < p; i++) this.scryptROMix(i, r); + + return this.B; + } + + scryptROMix(i: number, r: number) { + const blockStart = i * 128 * r; + const offset = (2 * r - 1) * 64; + const blockLen = 128 * r; + const B = this.B; + const N = this.N | 0; + const V = this.V; + const XY = this.XY; + B.copy(XY, 0, blockStart, blockStart + blockLen); + for (let i1 = 0; i1 < N; i1++) { + XY.copy(V, i1 * blockLen, 0, blockLen); + this.blockmix_salsa8(blockLen); + } + + let j: number; + for (let i2 = 0; i2 < N; i2++) { + j = XY.readUInt32LE(offset) & (N - 1); + blockxor(V, j * blockLen, XY, 0, blockLen); + this.blockmix_salsa8(blockLen); + } + XY.copy(B, blockStart, 0, blockLen); + } + + blockmix_salsa8(blockLen: number) { + const BY = this.XY; + const r = this.r; + const _X = this._X; + arraycopy(BY, (2 * r - 1) * 64, _X, 0, 64); + let i; + for (i = 0; i < 2 * r; i++) { + blockxor(BY, i * 64, _X, 0, 64); + this.salsa20_8(); + arraycopy(_X, 0, BY, blockLen + i * 64, 64); + } + for (i = 0; i < r; i++) { + arraycopy(BY, blockLen + i * 2 * 64, BY, i * 64, 64); + arraycopy(BY, blockLen + (i * 2 + 1) * 64, BY, (i + r) * 64, 64); + } + } + + salsa20_8() { + const B32 = this.B32; + const B = this._X; + const x = this.x; + + let i; + for (i = 0; i < 16; i++) { + B32[i] = (B[i * 4 + 0] & 0xff) << 0; + B32[i] |= (B[i * 4 + 1] & 0xff) << 8; + B32[i] |= (B[i * 4 + 2] & 0xff) << 16; + B32[i] |= (B[i * 4 + 3] & 0xff) << 24; + } + + for (i = 0; i < 16; i++) x[i] = B32[i]; + + for (i = 0; i < 4; i++) { + x[4] ^= R(x[0] + x[12], 7); + x[8] ^= R(x[4] + x[0], 9); + x[12] ^= R(x[8] + x[4], 13); + x[0] ^= R(x[12] + x[8], 18); + x[9] ^= R(x[5] + x[1], 7); + x[13] ^= R(x[9] + x[5], 9); + x[1] ^= R(x[13] + x[9], 13); + x[5] ^= R(x[1] + x[13], 18); + x[14] ^= R(x[10] + x[6], 7); + x[2] ^= R(x[14] + x[10], 9); + x[6] ^= R(x[2] + x[14], 13); + x[10] ^= R(x[6] + x[2], 18); + x[3] ^= R(x[15] + x[11], 7); + x[7] ^= R(x[3] + x[15], 9); + x[11] ^= R(x[7] + x[3], 13); + x[15] ^= R(x[11] + x[7], 18); + x[1] ^= R(x[0] + x[3], 7); + x[2] ^= R(x[1] + x[0], 9); + x[3] ^= R(x[2] + x[1], 13); + x[0] ^= R(x[3] + x[2], 18); + x[6] ^= R(x[5] + x[4], 7); + x[7] ^= R(x[6] + x[5], 9); + x[4] ^= R(x[7] + x[6], 13); + x[5] ^= R(x[4] + x[7], 18); + x[11] ^= R(x[10] + x[9], 7); + x[8] ^= R(x[11] + x[10], 9); + x[9] ^= R(x[8] + x[11], 13); + x[10] ^= R(x[9] + x[8], 18); + x[12] ^= R(x[15] + x[14], 7); + x[13] ^= R(x[12] + x[15], 9); + x[14] ^= R(x[13] + x[12], 13); + x[15] ^= R(x[14] + x[13], 18); + } + for (i = 0; i < 16; i++) B32[i] += x[i]; + + let bi; + + for (i = 0; i < 16; i++) { + bi = i * 4; + B[bi + 0] = (B32[i] >> 0) & 0xff; + B[bi + 1] = (B32[i] >> 8) & 0xff; + B[bi + 2] = (B32[i] >> 16) & 0xff; + B[bi + 3] = (B32[i] >> 24) & 0xff; + } + } + + clean() { + this.XY.fill(0); + this.V.fill(0); + this._X.fill(0); + this.B.fill(0); + for (let i = 0; i < 16; i++) { + this.B32[i] = 0; + this.x[i] = 0; + } + } +} + +export function scryptSync( + password: HASH_DATA, + salt: HASH_DATA, + keylen: number, + _opts?: Opts, +): Buffer { + const { N, r, p, maxmem } = fixOpts(_opts); + + const blen = p * 128 * r; + + if (32 * r * (N + 2) * 4 + blen > maxmem) { + throw new Error("excedes max memory"); + } + + const b = pbkdf2(password, salt, 1, blen, "sha256"); + + const scryptRom = new ScryptRom(b, r, N, p); + const out = scryptRom.run(); + + const fin = pbkdf2(password, out, 1, keylen, "sha256"); + scryptRom.clean(); + return fin; +} + +type Callback = (err: unknown, result?: Buffer) => void; + +export function scrypt( + password: HASH_DATA, + salt: HASH_DATA, + keylen: number, + _opts: Opts | null | Callback, + cb?: Callback, +) { + if (!cb) { + cb = _opts as Callback; + _opts = null; + } + const { N, r, p, maxmem } = fixOpts(_opts as Opts); + + const blen = p * 128 * r; + if (32 * r * (N + 2) * 4 + blen > maxmem) { + throw new Error("excedes max memory"); + } + + try { + const b = pbkdf2(password, salt, 1, blen, "sha256"); + + const scryptRom = new ScryptRom(b, r, N, p); + const out = scryptRom.run(); + const result = pbkdf2(password, out, 1, keylen, "sha256"); + scryptRom.clean(); + cb(null, result); + } catch (err: unknown) { + return cb(err); + } +} + +export default { + scrypt, + scryptSync, +}; diff --git a/ext/node/polyfills/internal/crypto/sig.ts b/ext/node/polyfills/internal/crypto/sig.ts new file mode 100644 index 000000000..6c163c8e5 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/sig.ts @@ -0,0 +1,148 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { validateString } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import type { WritableOptions } from "internal:deno_node/polyfills/_stream.d.ts"; +import Writable from "internal:deno_node/polyfills/internal/streams/writable.mjs"; +import type { + BinaryLike, + BinaryToTextEncoding, + Encoding, + PrivateKeyInput, + PublicKeyInput, +} from "internal:deno_node/polyfills/internal/crypto/types.ts"; +import { KeyObject } from "internal:deno_node/polyfills/internal/crypto/keys.ts"; + +export type DSAEncoding = "der" | "ieee-p1363"; + +export interface SigningOptions { + padding?: number | undefined; + saltLength?: number | undefined; + dsaEncoding?: DSAEncoding | undefined; +} + +export interface SignPrivateKeyInput extends PrivateKeyInput, SigningOptions {} + +export interface SignKeyObjectInput extends SigningOptions { + key: KeyObject; +} +export interface VerifyPublicKeyInput extends PublicKeyInput, SigningOptions {} + +export interface VerifyKeyObjectInput extends SigningOptions { + key: KeyObject; +} + +export type KeyLike = string | Buffer | KeyObject; + +export class Sign extends Writable { + constructor(algorithm: string, _options?: WritableOptions) { + validateString(algorithm, "algorithm"); + + super(); + + notImplemented("crypto.Sign"); + } + + sign(privateKey: KeyLike | SignKeyObjectInput | SignPrivateKeyInput): Buffer; + sign( + privateKey: KeyLike | SignKeyObjectInput | SignPrivateKeyInput, + outputFormat: BinaryToTextEncoding, + ): string; + sign( + _privateKey: KeyLike | SignKeyObjectInput | SignPrivateKeyInput, + _outputEncoding?: BinaryToTextEncoding, + ): Buffer | string { + notImplemented("crypto.Sign.prototype.sign"); + } + + update(data: BinaryLike): this; + update(data: string, inputEncoding: Encoding): this; + update(_data: BinaryLike | string, _inputEncoding?: Encoding): this { + notImplemented("crypto.Sign.prototype.update"); + } +} + +export class Verify extends Writable { + constructor(algorithm: string, _options?: WritableOptions) { + validateString(algorithm, "algorithm"); + + super(); + + notImplemented("crypto.Verify"); + } + + update(data: BinaryLike): this; + update(data: string, inputEncoding: Encoding): this; + update(_data: BinaryLike, _inputEncoding?: string): this { + notImplemented("crypto.Sign.prototype.update"); + } + + verify( + object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, + signature: ArrayBufferView, + ): boolean; + verify( + object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, + signature: string, + signatureEncoding?: BinaryToTextEncoding, + ): boolean; + verify( + _object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, + _signature: ArrayBufferView | string, + _signatureEncoding?: BinaryToTextEncoding, + ): boolean { + notImplemented("crypto.Sign.prototype.sign"); + } +} + +export function signOneShot( + algorithm: string | null | undefined, + data: ArrayBufferView, + key: KeyLike | SignKeyObjectInput | SignPrivateKeyInput, +): Buffer; +export function signOneShot( + algorithm: string | null | undefined, + data: ArrayBufferView, + key: KeyLike | SignKeyObjectInput | SignPrivateKeyInput, + callback: (error: Error | null, data: Buffer) => void, +): void; +export function signOneShot( + _algorithm: string | null | undefined, + _data: ArrayBufferView, + _key: KeyLike | SignKeyObjectInput | SignPrivateKeyInput, + _callback?: (error: Error | null, data: Buffer) => void, +): Buffer | void { + notImplemented("crypto.sign"); +} + +export function verifyOneShot( + algorithm: string | null | undefined, + data: ArrayBufferView, + key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, + signature: ArrayBufferView, +): boolean; +export function verifyOneShot( + algorithm: string | null | undefined, + data: ArrayBufferView, + key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, + signature: ArrayBufferView, + callback: (error: Error | null, result: boolean) => void, +): void; +export function verifyOneShot( + _algorithm: string | null | undefined, + _data: ArrayBufferView, + _key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, + _signature: ArrayBufferView, + _callback?: (error: Error | null, result: boolean) => void, +): boolean | void { + notImplemented("crypto.verify"); +} + +export default { + signOneShot, + verifyOneShot, + Sign, + Verify, +}; diff --git a/ext/node/polyfills/internal/crypto/types.ts b/ext/node/polyfills/internal/crypto/types.ts new file mode 100644 index 000000000..3bb9ec160 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/types.ts @@ -0,0 +1,46 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; + +export type HASH_DATA = string | ArrayBufferView | Buffer; + +export type BinaryToTextEncoding = "base64" | "base64url" | "hex" | "binary"; + +export type CharacterEncoding = "utf8" | "utf-8" | "utf16le" | "latin1"; + +export type LegacyCharacterEncoding = "ascii" | "binary" | "ucs2" | "ucs-2"; + +export type Encoding = + | BinaryToTextEncoding + | CharacterEncoding + | LegacyCharacterEncoding; + +export type ECDHKeyFormat = "compressed" | "uncompressed" | "hybrid"; + +export type BinaryLike = string | ArrayBufferView; + +export type KeyFormat = "pem" | "der"; + +export type KeyType = + | "rsa" + | "rsa-pss" + | "dsa" + | "ec" + | "ed25519" + | "ed448" + | "x25519" + | "x448"; + +export interface PrivateKeyInput { + key: string | Buffer; + format?: KeyFormat | undefined; + type?: "pkcs1" | "pkcs8" | "sec1" | undefined; + passphrase?: string | Buffer | undefined; +} + +export interface PublicKeyInput { + key: string | Buffer; + format?: KeyFormat | undefined; + type?: "pkcs1" | "spki" | undefined; +} diff --git a/ext/node/polyfills/internal/crypto/util.ts b/ext/node/polyfills/internal/crypto/util.ts new file mode 100644 index 000000000..f9fce8b2d --- /dev/null +++ b/ext/node/polyfills/internal/crypto/util.ts @@ -0,0 +1,129 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { getCiphers } from "internal:deno_node/polyfills/_crypto/crypto_browserify/browserify_aes/mod.js"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + ERR_INVALID_ARG_TYPE, + hideStackFrames, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { + isAnyArrayBuffer, + isArrayBufferView, +} from "internal:deno_node/polyfills/internal/util/types.ts"; +import { crypto as constants } from "internal:deno_node/polyfills/internal_binding/constants.ts"; +import { + kHandle, + kKeyObject, +} from "internal:deno_node/polyfills/internal/crypto/constants.ts"; + +// TODO(kt3k): Generate this list from `digestAlgorithms` +// of std/crypto/_wasm/mod.ts +const digestAlgorithms = [ + "blake2b256", + "blake2b384", + "blake2b", + "blake2s", + "blake3", + "keccak-224", + "keccak-256", + "keccak-384", + "keccak-512", + "sha384", + "sha3-224", + "sha3-256", + "sha3-384", + "sha3-512", + "shake128", + "shake256", + "tiger", + "rmd160", + "sha224", + "sha256", + "sha512", + "md4", + "md5", + "sha1", +]; + +let defaultEncoding = "buffer"; + +export function setDefaultEncoding(val: string) { + defaultEncoding = val; +} + +export function getDefaultEncoding(): string { + return defaultEncoding; +} + +// This is here because many functions accepted binary strings without +// any explicit encoding in older versions of node, and we don't want +// to break them unnecessarily. +export function toBuf(val: string | Buffer, encoding?: string): Buffer { + if (typeof val === "string") { + if (encoding === "buffer") { + encoding = "utf8"; + } + + return Buffer.from(val, encoding); + } + + return val; +} + +export const validateByteSource = hideStackFrames((val, name) => { + val = toBuf(val); + + if (isAnyArrayBuffer(val) || isArrayBufferView(val)) { + return; + } + + throw new ERR_INVALID_ARG_TYPE( + name, + ["string", "ArrayBuffer", "TypedArray", "DataView", "Buffer"], + val, + ); +}); + +/** + * Returns an array of the names of the supported hash algorithms, such as 'sha1'. + */ +export function getHashes(): readonly string[] { + return digestAlgorithms; +} + +export function getCurves(): readonly string[] { + notImplemented("crypto.getCurves"); +} + +export interface SecureHeapUsage { + total: number; + min: number; + used: number; + utilization: number; +} + +export function secureHeapUsed(): SecureHeapUsage { + notImplemented("crypto.secureHeapUsed"); +} + +export function setEngine(_engine: string, _flags: typeof constants) { + notImplemented("crypto.setEngine"); +} + +export { getCiphers, kHandle, kKeyObject }; + +export default { + getDefaultEncoding, + getHashes, + setDefaultEncoding, + getCiphers, + getCurves, + secureHeapUsed, + setEngine, + validateByteSource, + toBuf, + kHandle, + kKeyObject, +}; diff --git a/ext/node/polyfills/internal/crypto/x509.ts b/ext/node/polyfills/internal/crypto/x509.ts new file mode 100644 index 000000000..0722d7865 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/x509.ts @@ -0,0 +1,186 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { KeyObject } from "internal:deno_node/polyfills/internal/crypto/keys.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { isArrayBufferView } from "internal:deno_node/polyfills/internal/util/types.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { BinaryLike } from "internal:deno_node/polyfills/internal/crypto/types.ts"; + +// deno-lint-ignore no-explicit-any +export type PeerCertificate = any; + +export interface X509CheckOptions { + /** + * @default 'always' + */ + subject: "always" | "never"; + /** + * @default true + */ + wildcards: boolean; + /** + * @default true + */ + partialWildcards: boolean; + /** + * @default false + */ + multiLabelWildcards: boolean; + /** + * @default false + */ + singleLabelSubdomains: boolean; +} + +export class X509Certificate { + constructor(buffer: BinaryLike) { + if (typeof buffer === "string") { + buffer = Buffer.from(buffer); + } + + if (!isArrayBufferView(buffer)) { + throw new ERR_INVALID_ARG_TYPE( + "buffer", + ["string", "Buffer", "TypedArray", "DataView"], + buffer, + ); + } + + notImplemented("crypto.X509Certificate"); + } + + get ca(): boolean { + notImplemented("crypto.X509Certificate.prototype.ca"); + + return false; + } + + checkEmail( + _email: string, + _options?: Pick<X509CheckOptions, "subject">, + ): string | undefined { + notImplemented("crypto.X509Certificate.prototype.checkEmail"); + } + + checkHost(_name: string, _options?: X509CheckOptions): string | undefined { + notImplemented("crypto.X509Certificate.prototype.checkHost"); + } + + checkIP(_ip: string): string | undefined { + notImplemented("crypto.X509Certificate.prototype.checkIP"); + } + + checkIssued(_otherCert: X509Certificate): boolean { + notImplemented("crypto.X509Certificate.prototype.checkIssued"); + } + + checkPrivateKey(_privateKey: KeyObject): boolean { + notImplemented("crypto.X509Certificate.prototype.checkPrivateKey"); + } + + get fingerprint(): string { + notImplemented("crypto.X509Certificate.prototype.fingerprint"); + + return ""; + } + + get fingerprint256(): string { + notImplemented("crypto.X509Certificate.prototype.fingerprint256"); + + return ""; + } + + get fingerprint512(): string { + notImplemented("crypto.X509Certificate.prototype.fingerprint512"); + + return ""; + } + + get infoAccess(): string | undefined { + notImplemented("crypto.X509Certificate.prototype.infoAccess"); + + return ""; + } + + get issuer(): string { + notImplemented("crypto.X509Certificate.prototype.issuer"); + + return ""; + } + + get issuerCertificate(): X509Certificate | undefined { + notImplemented("crypto.X509Certificate.prototype.issuerCertificate"); + + return {} as X509Certificate; + } + + get keyUsage(): string[] { + notImplemented("crypto.X509Certificate.prototype.keyUsage"); + + return []; + } + + get publicKey(): KeyObject { + notImplemented("crypto.X509Certificate.prototype.publicKey"); + + return {} as KeyObject; + } + + get raw(): Buffer { + notImplemented("crypto.X509Certificate.prototype.raw"); + + return {} as Buffer; + } + + get serialNumber(): string { + notImplemented("crypto.X509Certificate.prototype.serialNumber"); + + return ""; + } + + get subject(): string { + notImplemented("crypto.X509Certificate.prototype.subject"); + + return ""; + } + + get subjectAltName(): string | undefined { + notImplemented("crypto.X509Certificate.prototype.subjectAltName"); + + return ""; + } + + toJSON(): string { + return this.toString(); + } + + toLegacyObject(): PeerCertificate { + notImplemented("crypto.X509Certificate.prototype.toLegacyObject"); + } + + toString(): string { + notImplemented("crypto.X509Certificate.prototype.toString"); + } + + get validFrom(): string { + notImplemented("crypto.X509Certificate.prototype.validFrom"); + + return ""; + } + + get validTo(): string { + notImplemented("crypto.X509Certificate.prototype.validTo"); + + return ""; + } + + verify(_publicKey: KeyObject): boolean { + notImplemented("crypto.X509Certificate.prototype.verify"); + } +} + +export default { + X509Certificate, +}; diff --git a/ext/node/polyfills/internal/dgram.ts b/ext/node/polyfills/internal/dgram.ts new file mode 100644 index 000000000..8e8e50fab --- /dev/null +++ b/ext/node/polyfills/internal/dgram.ts @@ -0,0 +1,129 @@ +// Copyright 2018-2023 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 { lookup as defaultLookup } from "internal:deno_node/polyfills/dns.ts"; +import { + isInt32, + validateFunction, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import type { ErrnoException } from "internal:deno_node/polyfills/internal/errors.ts"; +import { ERR_SOCKET_BAD_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { UDP } from "internal:deno_node/polyfills/internal_binding/udp_wrap.ts"; +import { guessHandleType } from "internal:deno_node/polyfills/internal_binding/util.ts"; +import { codeMap } from "internal:deno_node/polyfills/internal_binding/uv.ts"; + +export type SocketType = "udp4" | "udp6"; + +export const kStateSymbol: unique symbol = Symbol("kStateSymbol"); + +function lookup4( + lookup: typeof defaultLookup, + address: string, + callback: ( + err: ErrnoException | null, + address: string, + family: number, + ) => void, +) { + return lookup(address || "127.0.0.1", 4, callback); +} + +function lookup6( + lookup: typeof defaultLookup, + address: string, + callback: ( + err: ErrnoException | null, + address: string, + family: number, + ) => void, +) { + return lookup(address || "::1", 6, callback); +} + +export function newHandle( + type: SocketType, + lookup?: typeof defaultLookup, +): UDP { + if (lookup === undefined) { + lookup = defaultLookup; + } else { + validateFunction(lookup, "lookup"); + } + + if (type === "udp4") { + const handle = new UDP(); + + handle.lookup = lookup4.bind(handle, lookup); + + return handle; + } + + if (type === "udp6") { + const handle = new UDP(); + + handle.lookup = lookup6.bind(handle, lookup); + handle.bind = handle.bind6; + handle.connect = handle.connect6; + handle.send = handle.send6; + + return handle; + } + + throw new ERR_SOCKET_BAD_TYPE(); +} + +export function _createSocketHandle( + address: string, + port: number, + addressType: SocketType, + fd: number, + flags: number, +) { + const handle = newHandle(addressType); + let err; + + if (isInt32(fd) && fd > 0) { + const type = guessHandleType(fd); + + if (type !== "UDP") { + err = codeMap.get("EINVAL")!; + } else { + err = handle.open(fd); + } + } else if (port || address) { + err = handle.bind(address, port || 0, flags); + } + + if (err) { + handle.close(); + + return err; + } + + return handle; +} + +export default { + kStateSymbol, + newHandle, + _createSocketHandle, +}; diff --git a/ext/node/polyfills/internal/dns/promises.ts b/ext/node/polyfills/internal/dns/promises.ts new file mode 100644 index 000000000..e5e15dc87 --- /dev/null +++ b/ext/node/polyfills/internal/dns/promises.ts @@ -0,0 +1,511 @@ +// Copyright 2018-2023 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 { + validateBoolean, + validateNumber, + validateOneOf, + validateString, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { isIP } from "internal:deno_node/polyfills/internal/net.ts"; +import { + emitInvalidHostnameWarning, + getDefaultResolver, + getDefaultVerbatim, + isFamily, + isLookupOptions, + Resolver as CallbackResolver, + validateHints, +} from "internal:deno_node/polyfills/internal/dns/utils.ts"; +import type { + LookupAddress, + LookupAllOptions, + LookupOneOptions, + LookupOptions, + Records, + ResolveOptions, + ResolveWithTtlOptions, +} from "internal:deno_node/polyfills/internal/dns/utils.ts"; +import { + dnsException, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { + ChannelWrapQuery, + getaddrinfo, + GetAddrInfoReqWrap, + QueryReqWrap, +} from "internal:deno_node/polyfills/internal_binding/cares_wrap.ts"; +import { toASCII } from "internal:deno_node/polyfills/internal/idna.ts"; + +function onlookup( + this: GetAddrInfoReqWrap, + err: number | null, + addresses: string[], +) { + if (err) { + this.reject(dnsException(err, "getaddrinfo", this.hostname)); + return; + } + + const family = this.family || isIP(addresses[0]); + this.resolve({ address: addresses[0], family }); +} + +function onlookupall( + this: GetAddrInfoReqWrap, + err: number | null, + addresses: string[], +) { + if (err) { + this.reject(dnsException(err, "getaddrinfo", this.hostname)); + + return; + } + + const family = this.family; + const parsedAddresses = []; + + for (let i = 0; i < addresses.length; i++) { + const address = addresses[i]; + parsedAddresses[i] = { + address, + family: family ? family : isIP(address), + }; + } + + this.resolve(parsedAddresses); +} + +function createLookupPromise( + family: number, + hostname: string, + all: boolean, + hints: number, + verbatim: boolean, +): Promise<void | LookupAddress | LookupAddress[]> { + return new Promise((resolve, reject) => { + if (!hostname) { + emitInvalidHostnameWarning(hostname); + resolve(all ? [] : { address: null, family: family === 6 ? 6 : 4 }); + + return; + } + + const matchedFamily = isIP(hostname); + + if (matchedFamily !== 0) { + const result = { address: hostname, family: matchedFamily }; + resolve(all ? [result] : result); + + return; + } + + const req = new GetAddrInfoReqWrap(); + + req.family = family; + req.hostname = hostname; + req.oncomplete = all ? onlookupall : onlookup; + req.resolve = resolve; + req.reject = reject; + + const err = getaddrinfo(req, toASCII(hostname), family, hints, verbatim); + + if (err) { + reject(dnsException(err, "getaddrinfo", hostname)); + } + }); +} + +const validFamilies = [0, 4, 6]; + +export function lookup( + hostname: string, + family: number, +): Promise<void | LookupAddress | LookupAddress[]>; +export function lookup( + hostname: string, + options: LookupOneOptions, +): Promise<void | LookupAddress | LookupAddress[]>; +export function lookup( + hostname: string, + options: LookupAllOptions, +): Promise<void | LookupAddress | LookupAddress[]>; +export function lookup( + hostname: string, + options: LookupOptions, +): Promise<void | LookupAddress | LookupAddress[]>; +export function lookup( + hostname: string, + options: unknown, +): Promise<void | LookupAddress | LookupAddress[]> { + let hints = 0; + let family = 0; + let all = false; + let verbatim = getDefaultVerbatim(); + + // Parse arguments + if (hostname) { + validateString(hostname, "hostname"); + } + + if (isFamily(options)) { + validateOneOf(options, "family", validFamilies); + family = options; + } else if (!isLookupOptions(options)) { + throw new ERR_INVALID_ARG_TYPE("options", ["integer", "object"], options); + } else { + if (options?.hints != null) { + validateNumber(options.hints, "options.hints"); + hints = options.hints >>> 0; + validateHints(hints); + } + + if (options?.family != null) { + validateOneOf(options.family, "options.family", validFamilies); + family = options.family; + } + + if (options?.all != null) { + validateBoolean(options.all, "options.all"); + all = options.all; + } + + if (options?.verbatim != null) { + validateBoolean(options.verbatim, "options.verbatim"); + verbatim = options.verbatim; + } + } + + return createLookupPromise(family, hostname, all, hints, verbatim); +} + +function onresolve( + this: QueryReqWrap, + err: number, + records: Records, + ttls?: number[], +) { + if (err) { + this.reject(dnsException(err, this.bindingName, this.hostname)); + + return; + } + + const parsedRecords = ttls && this.ttl + ? (records as string[]).map((address: string, index: number) => ({ + address, + ttl: ttls[index], + })) + : records; + + this.resolve(parsedRecords); +} + +function createResolverPromise( + resolver: Resolver, + bindingName: keyof ChannelWrapQuery, + hostname: string, + ttl: boolean, +) { + return new Promise((resolve, reject) => { + const req = new QueryReqWrap(); + + req.bindingName = bindingName; + req.hostname = hostname; + req.oncomplete = onresolve; + req.resolve = resolve; + req.reject = reject; + req.ttl = ttl; + + const err = resolver._handle[bindingName](req, toASCII(hostname)); + + if (err) { + reject(dnsException(err, bindingName, hostname)); + } + }); +} + +function resolver(bindingName: keyof ChannelWrapQuery) { + function query( + this: Resolver, + name: string, + options?: unknown, + ) { + validateString(name, "name"); + + const ttl = !!(options && (options as ResolveOptions).ttl); + + return createResolverPromise(this, bindingName, name, ttl); + } + + Object.defineProperty(query, "name", { value: bindingName }); + + return query; +} + +const resolveMap = Object.create(null); + +class Resolver extends CallbackResolver { + // deno-lint-ignore no-explicit-any + [resolveMethod: string]: any; +} + +Resolver.prototype.resolveAny = resolveMap.ANY = resolver("queryAny"); +Resolver.prototype.resolve4 = resolveMap.A = resolver("queryA"); +Resolver.prototype.resolve6 = resolveMap.AAAA = resolver("queryAaaa"); +Resolver.prototype.resolveCaa = resolveMap.CAA = resolver("queryCaa"); +Resolver.prototype.resolveCname = resolveMap.CNAME = resolver("queryCname"); +Resolver.prototype.resolveMx = resolveMap.MX = resolver("queryMx"); +Resolver.prototype.resolveNs = resolveMap.NS = resolver("queryNs"); +Resolver.prototype.resolveTxt = resolveMap.TXT = resolver("queryTxt"); +Resolver.prototype.resolveSrv = resolveMap.SRV = resolver("querySrv"); +Resolver.prototype.resolvePtr = resolveMap.PTR = resolver("queryPtr"); +Resolver.prototype.resolveNaptr = resolveMap.NAPTR = resolver("queryNaptr"); +Resolver.prototype.resolveSoa = resolveMap.SOA = resolver("querySoa"); +Resolver.prototype.reverse = resolver("getHostByAddr"); +Resolver.prototype.resolve = _resolve; + +function _resolve( + this: Resolver, + hostname: string, + rrtype?: string, +) { + let resolver; + + if (typeof hostname !== "string") { + throw new ERR_INVALID_ARG_TYPE("name", "string", hostname); + } + + if (rrtype !== undefined) { + validateString(rrtype, "rrtype"); + + resolver = resolveMap[rrtype]; + + if (typeof resolver !== "function") { + throw new ERR_INVALID_ARG_VALUE("rrtype", rrtype); + } + } else { + resolver = resolveMap.A; + } + + return Reflect.apply(resolver, this, [hostname]); +} + +// The Node implementation uses `bindDefaultResolver` to set the follow methods +// on `module.exports` bound to the current `defaultResolver`. We don't have +// the same ability in ESM but can simulate this (at some cost) by explicitly +// exporting these methods which dynamically bind to the default resolver when +// called. + +export function getServers(): string[] { + return Resolver.prototype.getServers.bind(getDefaultResolver())(); +} + +export function resolveAny( + hostname: string, +) { + return Resolver.prototype.resolveAny.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolve4( + hostname: string, +): Promise<void>; +export function resolve4( + hostname: string, + options: ResolveWithTtlOptions, +): Promise<void>; +export function resolve4( + hostname: string, + options: ResolveOptions, +): Promise<void>; +export function resolve4(hostname: string, options?: unknown) { + return Resolver.prototype.resolve4.bind(getDefaultResolver() as Resolver)( + hostname, + options, + ); +} + +export function resolve6(hostname: string): Promise<void>; +export function resolve6( + hostname: string, + options: ResolveWithTtlOptions, +): Promise<void>; +export function resolve6( + hostname: string, + options: ResolveOptions, +): Promise<void>; +export function resolve6(hostname: string, options?: unknown) { + return Resolver.prototype.resolve6.bind(getDefaultResolver() as Resolver)( + hostname, + options, + ); +} + +export function resolveCaa( + hostname: string, +) { + return Resolver.prototype.resolveCaa.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolveCname( + hostname: string, +) { + return Resolver.prototype.resolveCname.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolveMx( + hostname: string, +) { + return Resolver.prototype.resolveMx.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolveNs(hostname: string) { + return Resolver.prototype.resolveNs.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolveTxt(hostname: string) { + return Resolver.prototype.resolveTxt.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolveSrv(hostname: string) { + return Resolver.prototype.resolveSrv.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolvePtr(hostname: string) { + return Resolver.prototype.resolvePtr.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolveNaptr(hostname: string) { + return Resolver.prototype.resolveNaptr.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolveSoa(hostname: string) { + return Resolver.prototype.resolveSoa.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function reverse(ip: string) { + return Resolver.prototype.reverse.bind(getDefaultResolver() as Resolver)( + ip, + ); +} + +export function resolve( + hostname: string, +): Promise<void>; +export function resolve( + hostname: string, + rrtype: "A", +): Promise<void>; +export function resolve( + hostname: string, + rrtype: "AAAA", +): Promise<void>; +export function resolve( + hostname: string, + rrtype: "ANY", +): Promise<void>; +export function resolve( + hostname: string, + rrtype: "CNAME", +): Promise<void>; +export function resolve( + hostname: string, + rrtype: "MX", +): Promise<void>; +export function resolve( + hostname: string, + rrtype: "NAPTR", +): Promise<void>; +export function resolve( + hostname: string, + rrtype: "NS", +): Promise<void>; +export function resolve( + hostname: string, + rrtype: "PTR", +): Promise<void>; +export function resolve( + hostname: string, + rrtype: "SOA", +): Promise<void>; +export function resolve( + hostname: string, + rrtype: "SRV", +): Promise<void>; +export function resolve( + hostname: string, + rrtype: "TXT", +): Promise<void>; +export function resolve( + hostname: string, + rrtype: string, +): Promise<void>; +export function resolve(hostname: string, rrtype?: string) { + return Resolver.prototype.resolve.bind(getDefaultResolver() as Resolver)( + hostname, + rrtype, + ); +} + +export { Resolver }; + +export default { + lookup, + Resolver, + getServers, + resolveAny, + resolve4, + resolve6, + resolveCaa, + resolveCname, + resolveMx, + resolveNs, + resolveTxt, + resolveSrv, + resolvePtr, + resolveNaptr, + resolveSoa, + resolve, + reverse, +}; diff --git a/ext/node/polyfills/internal/dns/utils.ts b/ext/node/polyfills/internal/dns/utils.ts new file mode 100644 index 000000000..0afd10617 --- /dev/null +++ b/ext/node/polyfills/internal/dns/utils.ts @@ -0,0 +1,456 @@ +// Copyright 2018-2023 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 { getOptionValue } from "internal:deno_node/polyfills/internal/options.ts"; +import { emitWarning } from "internal:deno_node/polyfills/process.ts"; +import { + AI_ADDRCONFIG, + AI_ALL, + AI_V4MAPPED, +} from "internal:deno_node/polyfills/internal_binding/ares.ts"; +import { + ChannelWrap, + strerror, +} from "internal:deno_node/polyfills/internal_binding/cares_wrap.ts"; +import { + ERR_DNS_SET_SERVERS_FAILED, + ERR_INVALID_ARG_VALUE, + ERR_INVALID_IP_ADDRESS, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import type { ErrnoException } from "internal:deno_node/polyfills/internal/errors.ts"; +import { + validateArray, + validateInt32, + validateOneOf, + validateString, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { isIP } from "internal:deno_node/polyfills/internal/net.ts"; + +export interface LookupOptions { + family?: number | undefined; + hints?: number | undefined; + all?: boolean | undefined; + verbatim?: boolean | undefined; +} + +export interface LookupOneOptions extends LookupOptions { + all?: false | undefined; +} + +export interface LookupAllOptions extends LookupOptions { + all: true; +} + +export interface LookupAddress { + address: string | null; + family: number; +} + +export function isLookupOptions( + options: unknown, +): options is LookupOptions | undefined { + return typeof options === "object" || typeof options === "undefined"; +} + +export function isLookupCallback( + options: unknown, +): options is (...args: unknown[]) => void { + return typeof options === "function"; +} + +export function isFamily(options: unknown): options is number { + return typeof options === "number"; +} + +export interface ResolveOptions { + ttl?: boolean; +} + +export interface ResolveWithTtlOptions extends ResolveOptions { + ttl: true; +} + +export interface RecordWithTtl { + address: string; + ttl: number; +} + +export interface AnyARecord extends RecordWithTtl { + type: "A"; +} + +export interface AnyAaaaRecord extends RecordWithTtl { + type: "AAAA"; +} + +export interface CaaRecord { + critial: number; + issue?: string | undefined; + issuewild?: string | undefined; + iodef?: string | undefined; + contactemail?: string | undefined; + contactphone?: string | undefined; +} + +export interface MxRecord { + priority: number; + exchange: string; +} + +export interface AnyMxRecord extends MxRecord { + type: "MX"; +} + +export interface NaptrRecord { + flags: string; + service: string; + regexp: string; + replacement: string; + order: number; + preference: number; +} + +export interface AnyNaptrRecord extends NaptrRecord { + type: "NAPTR"; +} + +export interface SoaRecord { + nsname: string; + hostmaster: string; + serial: number; + refresh: number; + retry: number; + expire: number; + minttl: number; +} + +export interface AnySoaRecord extends SoaRecord { + type: "SOA"; +} + +export interface SrvRecord { + priority: number; + weight: number; + port: number; + name: string; +} + +export interface AnySrvRecord extends SrvRecord { + type: "SRV"; +} + +export interface AnyTxtRecord { + type: "TXT"; + entries: string[]; +} + +export interface AnyNsRecord { + type: "NS"; + value: string; +} + +export interface AnyPtrRecord { + type: "PTR"; + value: string; +} + +export interface AnyCnameRecord { + type: "CNAME"; + value: string; +} + +export type AnyRecord = + | AnyARecord + | AnyAaaaRecord + | AnyCnameRecord + | AnyMxRecord + | AnyNaptrRecord + | AnyNsRecord + | AnyPtrRecord + | AnySoaRecord + | AnySrvRecord + | AnyTxtRecord; + +export type Records = + | string[] + | AnyRecord[] + | MxRecord[] + | NaptrRecord[] + | SoaRecord + | SrvRecord[] + | string[]; + +export type ResolveCallback = ( + err: ErrnoException | null, + addresses: Records, +) => void; + +export function isResolveCallback( + callback: unknown, +): callback is ResolveCallback { + return typeof callback === "function"; +} + +const IANA_DNS_PORT = 53; +const IPv6RE = /^\[([^[\]]*)\]/; +const addrSplitRE = /(^.+?)(?::(\d+))?$/; + +export function validateTimeout(options?: { timeout?: number }) { + const { timeout = -1 } = { ...options }; + validateInt32(timeout, "options.timeout", -1, 2 ** 31 - 1); + return timeout; +} + +export function validateTries(options?: { tries?: number }) { + const { tries = 4 } = { ...options }; + validateInt32(tries, "options.tries", 1, 2 ** 31 - 1); + return tries; +} + +export interface ResolverOptions { + timeout?: number | undefined; + /** + * @default 4 + */ + tries?: number; +} + +/** + * An independent resolver for DNS requests. + * + * Creating a new resolver uses the default server settings. Setting + * the servers used for a resolver using `resolver.setServers()` does not affect + * other resolvers: + * + * ```js + * const { Resolver } = require('dns'); + * const resolver = new Resolver(); + * resolver.setServers(['4.4.4.4']); + * + * // This request will use the server at 4.4.4.4, independent of global settings. + * resolver.resolve4('example.org', (err, addresses) => { + * // ... + * }); + * ``` + * + * The following methods from the `dns` module are available: + * + * - `resolver.getServers()` + * - `resolver.resolve()` + * - `resolver.resolve4()` + * - `resolver.resolve6()` + * - `resolver.resolveAny()` + * - `resolver.resolveCaa()` + * - `resolver.resolveCname()` + * - `resolver.resolveMx()` + * - `resolver.resolveNaptr()` + * - `resolver.resolveNs()` + * - `resolver.resolvePtr()` + * - `resolver.resolveSoa()` + * - `resolver.resolveSrv()` + * - `resolver.resolveTxt()` + * - `resolver.reverse()` + * - `resolver.setServers()` + */ +export class Resolver { + _handle!: ChannelWrap; + + constructor(options?: ResolverOptions) { + const timeout = validateTimeout(options); + const tries = validateTries(options); + this._handle = new ChannelWrap(timeout, tries); + } + + cancel() { + this._handle.cancel(); + } + + getServers(): string[] { + return this._handle.getServers().map((val: [string, number]) => { + if (!val[1] || val[1] === IANA_DNS_PORT) { + return val[0]; + } + + const host = isIP(val[0]) === 6 ? `[${val[0]}]` : val[0]; + return `${host}:${val[1]}`; + }); + } + + setServers(servers: ReadonlyArray<string>) { + validateArray(servers, "servers"); + + // Cache the original servers because in the event of an error while + // setting the servers, c-ares won't have any servers available for + // resolution. + const orig = this._handle.getServers(); + const newSet: [number, string, number][] = []; + + servers.forEach((serv, index) => { + validateString(serv, `servers[${index}]`); + let ipVersion = isIP(serv); + + if (ipVersion !== 0) { + return newSet.push([ipVersion, serv, IANA_DNS_PORT]); + } + + const match = serv.match(IPv6RE); + + // Check for an IPv6 in brackets. + if (match) { + ipVersion = isIP(match[1]); + + if (ipVersion !== 0) { + const port = Number.parseInt(serv.replace(addrSplitRE, "$2")) || + IANA_DNS_PORT; + + return newSet.push([ipVersion, match[1], port]); + } + } + + // addr::port + const addrSplitMatch = serv.match(addrSplitRE); + + if (addrSplitMatch) { + const hostIP = addrSplitMatch[1]; + const port = addrSplitMatch[2] || `${IANA_DNS_PORT}`; + + ipVersion = isIP(hostIP); + + if (ipVersion !== 0) { + return newSet.push([ipVersion, hostIP, Number.parseInt(port)]); + } + } + + throw new ERR_INVALID_IP_ADDRESS(serv); + }); + + const errorNumber = this._handle.setServers(newSet); + + if (errorNumber !== 0) { + // Reset the servers to the old servers, because ares probably unset them. + this._handle.setServers(orig.join(",")); + const err = strerror(errorNumber); + + throw new ERR_DNS_SET_SERVERS_FAILED(err, servers.toString()); + } + } + + /** + * The resolver instance will send its requests from the specified IP address. + * This allows programs to specify outbound interfaces when used on multi-homed + * systems. + * + * If a v4 or v6 address is not specified, it is set to the default, and the + * operating system will choose a local address automatically. + * + * The resolver will use the v4 local address when making requests to IPv4 DNS + * servers, and the v6 local address when making requests to IPv6 DNS servers. + * The `rrtype` of resolution requests has no impact on the local address used. + * + * @param [ipv4='0.0.0.0'] A string representation of an IPv4 address. + * @param [ipv6='::0'] A string representation of an IPv6 address. + */ + setLocalAddress(ipv4: string, ipv6?: string) { + validateString(ipv4, "ipv4"); + + if (ipv6 !== undefined) { + validateString(ipv6, "ipv6"); + } + + this._handle.setLocalAddress(ipv4, ipv6); + } +} + +let defaultResolver = new Resolver(); + +export function getDefaultResolver(): Resolver { + return defaultResolver; +} + +export function setDefaultResolver<T extends Resolver>(resolver: T) { + defaultResolver = resolver; +} + +export function validateHints(hints: number) { + if ((hints & ~(AI_ADDRCONFIG | AI_ALL | AI_V4MAPPED)) !== 0) { + throw new ERR_INVALID_ARG_VALUE("hints", hints, "is invalid"); + } +} + +let invalidHostnameWarningEmitted = false; + +export function emitInvalidHostnameWarning(hostname: string) { + if (invalidHostnameWarningEmitted) { + return; + } + + invalidHostnameWarningEmitted = true; + + emitWarning( + `The provided hostname "${hostname}" is not a valid ` + + "hostname, and is supported in the dns module solely for compatibility.", + "DeprecationWarning", + "DEP0118", + ); +} + +let dnsOrder = getOptionValue("--dns-result-order") || "ipv4first"; + +export function getDefaultVerbatim() { + switch (dnsOrder) { + case "verbatim": { + return true; + } + case "ipv4first": { + return false; + } + default: { + return false; + } + } +} + +/** + * Set the default value of `verbatim` in `lookup` and `dnsPromises.lookup()`. + * The value could be: + * + * - `ipv4first`: sets default `verbatim` `false`. + * - `verbatim`: sets default `verbatim` `true`. + * + * The default is `ipv4first` and `setDefaultResultOrder` have higher + * priority than `--dns-result-order`. When using `worker threads`, + * `setDefaultResultOrder` from the main thread won't affect the default + * dns orders in workers. + * + * @param order must be `'ipv4first'` or `'verbatim'`. + */ +export function setDefaultResultOrder(order: "ipv4first" | "verbatim") { + validateOneOf(order, "dnsOrder", ["verbatim", "ipv4first"]); + dnsOrder = order; +} + +export function defaultResolverSetServers(servers: string[]) { + const resolver = new Resolver(); + + resolver.setServers(servers); + setDefaultResolver(resolver); +} diff --git a/ext/node/polyfills/internal/dtrace.ts b/ext/node/polyfills/internal/dtrace.ts new file mode 100644 index 000000000..5a4470336 --- /dev/null +++ b/ext/node/polyfills/internal/dtrace.ts @@ -0,0 +1,41 @@ +// Copyright 2018-2023 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. + +// deno-lint-ignore-file + +const { + DTRACE_HTTP_CLIENT_REQUEST = (..._args: any[]) => {}, + DTRACE_HTTP_CLIENT_RESPONSE = (..._args: any[]) => {}, + DTRACE_HTTP_SERVER_REQUEST = (..._args: any[]) => {}, + DTRACE_HTTP_SERVER_RESPONSE = (..._args: any[]) => {}, + DTRACE_NET_SERVER_CONNECTION = (..._args: any[]) => {}, + DTRACE_NET_STREAM_END = (..._args: any[]) => {}, +} = {}; + +export { + DTRACE_HTTP_CLIENT_REQUEST, + DTRACE_HTTP_CLIENT_RESPONSE, + DTRACE_HTTP_SERVER_REQUEST, + DTRACE_HTTP_SERVER_RESPONSE, + DTRACE_NET_SERVER_CONNECTION, + DTRACE_NET_STREAM_END, +}; diff --git a/ext/node/polyfills/internal/error_codes.ts b/ext/node/polyfills/internal/error_codes.ts new file mode 100644 index 000000000..6af88bb11 --- /dev/null +++ b/ext/node/polyfills/internal/error_codes.ts @@ -0,0 +1,7 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// Lazily initializes the error classes in this object. +// This trick is necessary for avoiding circular dendencies between +// `internal/errors` and other modules. +// deno-lint-ignore no-explicit-any +export const codes: Record<string, any> = {}; diff --git a/ext/node/polyfills/internal/errors.ts b/ext/node/polyfills/internal/errors.ts new file mode 100644 index 000000000..67f729f8d --- /dev/null +++ b/ext/node/polyfills/internal/errors.ts @@ -0,0 +1,2864 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Node.js contributors. All rights reserved. MIT License. +/** NOT IMPLEMENTED + * ERR_MANIFEST_ASSERT_INTEGRITY + * ERR_QUICSESSION_VERSION_NEGOTIATION + * ERR_REQUIRE_ESM + * ERR_TLS_CERT_ALTNAME_INVALID + * ERR_WORKER_INVALID_EXEC_ARGV + * ERR_WORKER_PATH + * ERR_QUIC_ERROR + * ERR_SYSTEM_ERROR //System error, shouldn't ever happen inside Deno + * ERR_TTY_INIT_FAILED //System error, shouldn't ever happen inside Deno + * ERR_INVALID_PACKAGE_CONFIG // package.json stuff, probably useless + */ + +import { inspect } from "internal:deno_node/polyfills/internal/util/inspect.mjs"; +import { codes } from "internal:deno_node/polyfills/internal/error_codes.ts"; +import { + codeMap, + errorMap, + mapSysErrnoToUvErrno, +} from "internal:deno_node/polyfills/internal_binding/uv.ts"; +import { assert } from "internal:deno_node/polyfills/_util/asserts.ts"; +import { isWindows } from "internal:deno_node/polyfills/_util/os.ts"; +import { os as osConstants } from "internal:deno_node/polyfills/internal_binding/constants.ts"; +import { hideStackFrames } from "internal:deno_node/polyfills/internal/hide_stack_frames.ts"; +import { getSystemErrorName } from "internal:deno_node/polyfills/_utils.ts"; + +export { errorMap }; + +const kIsNodeError = Symbol("kIsNodeError"); + +/** + * @see https://github.com/nodejs/node/blob/f3eb224/lib/internal/errors.js + */ +const classRegExp = /^([A-Z][a-z0-9]*)+$/; + +/** + * @see https://github.com/nodejs/node/blob/f3eb224/lib/internal/errors.js + * @description Sorted by a rough estimate on most frequently used entries. + */ +const kTypes = [ + "string", + "function", + "number", + "object", + // Accept 'Function' and 'Object' as alternative to the lower cased version. + "Function", + "Object", + "boolean", + "bigint", + "symbol", +]; + +// Node uses an AbortError that isn't exactly the same as the DOMException +// to make usage of the error in userland and readable-stream easier. +// It is a regular error with `.code` and `.name`. +export class AbortError extends Error { + code: string; + + constructor(message = "The operation was aborted", options?: ErrorOptions) { + if (options !== undefined && typeof options !== "object") { + throw new codes.ERR_INVALID_ARG_TYPE("options", "Object", options); + } + super(message, options); + this.code = "ABORT_ERR"; + this.name = "AbortError"; + } +} + +let maxStackErrorName: string | undefined; +let maxStackErrorMessage: string | undefined; +/** + * Returns true if `err.name` and `err.message` are equal to engine-specific + * values indicating max call stack size has been exceeded. + * "Maximum call stack size exceeded" in V8. + */ +export function isStackOverflowError(err: Error): boolean { + if (maxStackErrorMessage === undefined) { + try { + // deno-lint-ignore no-inner-declarations + function overflowStack() { + overflowStack(); + } + overflowStack(); + // deno-lint-ignore no-explicit-any + } catch (err: any) { + maxStackErrorMessage = err.message; + maxStackErrorName = err.name; + } + } + + return err && err.name === maxStackErrorName && + err.message === maxStackErrorMessage; +} + +function addNumericalSeparator(val: string) { + let res = ""; + let i = val.length; + const start = val[0] === "-" ? 1 : 0; + for (; i >= start + 4; i -= 3) { + res = `_${val.slice(i - 3, i)}${res}`; + } + return `${val.slice(0, i)}${res}`; +} + +const captureLargerStackTrace = hideStackFrames( + function captureLargerStackTrace(err) { + // @ts-ignore this function is not available in lib.dom.d.ts + Error.captureStackTrace(err); + + return err; + }, +); + +export interface ErrnoException extends Error { + errno?: number; + code?: string; + path?: string; + syscall?: string; + spawnargs?: string[]; +} + +/** + * This creates an error compatible with errors produced in the C++ + * This function should replace the deprecated + * `exceptionWithHostPort()` function. + * + * @param err A libuv error number + * @param syscall + * @param address + * @param port + * @return The error. + */ +export const uvExceptionWithHostPort = hideStackFrames( + function uvExceptionWithHostPort( + err: number, + syscall: string, + address?: string | null, + port?: number | null, + ) { + const { 0: code, 1: uvmsg } = uvErrmapGet(err) || uvUnmappedError; + const message = `${syscall} ${code}: ${uvmsg}`; + let details = ""; + + if (port && port > 0) { + details = ` ${address}:${port}`; + } else if (address) { + details = ` ${address}`; + } + + // deno-lint-ignore no-explicit-any + const ex: any = new Error(`${message}${details}`); + ex.code = code; + ex.errno = err; + ex.syscall = syscall; + ex.address = address; + + if (port) { + ex.port = port; + } + + return captureLargerStackTrace(ex); + }, +); + +/** + * This used to be `util._errnoException()`. + * + * @param err A libuv error number + * @param syscall + * @param original + * @return A `ErrnoException` + */ +export const errnoException = hideStackFrames(function errnoException( + err, + syscall, + original?, +): ErrnoException { + const code = getSystemErrorName(err); + const message = original + ? `${syscall} ${code} ${original}` + : `${syscall} ${code}`; + + // deno-lint-ignore no-explicit-any + const ex: any = new Error(message); + ex.errno = err; + ex.code = code; + ex.syscall = syscall; + + return captureLargerStackTrace(ex); +}); + +function uvErrmapGet(name: number) { + return errorMap.get(name); +} + +const uvUnmappedError = ["UNKNOWN", "unknown error"]; + +/** + * This creates an error compatible with errors produced in the C++ + * function UVException using a context object with data assembled in C++. + * The goal is to migrate them to ERR_* errors later when compatibility is + * not a concern. + * + * @param ctx + * @return The error. + */ +export const uvException = hideStackFrames(function uvException(ctx) { + const { 0: code, 1: uvmsg } = uvErrmapGet(ctx.errno) || uvUnmappedError; + + let message = `${code}: ${ctx.message || uvmsg}, ${ctx.syscall}`; + + let path; + let dest; + + if (ctx.path) { + path = ctx.path.toString(); + message += ` '${path}'`; + } + if (ctx.dest) { + dest = ctx.dest.toString(); + message += ` -> '${dest}'`; + } + + // deno-lint-ignore no-explicit-any + const err: any = new Error(message); + + for (const prop of Object.keys(ctx)) { + if (prop === "message" || prop === "path" || prop === "dest") { + continue; + } + + err[prop] = ctx[prop]; + } + + err.code = code; + + if (path) { + err.path = path; + } + + if (dest) { + err.dest = dest; + } + + return captureLargerStackTrace(err); +}); + +/** + * Deprecated, new function is `uvExceptionWithHostPort()` + * New function added the error description directly + * from C++. this method for backwards compatibility + * @param err A libuv error number + * @param syscall + * @param address + * @param port + * @param additional + */ +export const exceptionWithHostPort = hideStackFrames( + function exceptionWithHostPort( + err: number, + syscall: string, + address: string, + port: number, + additional?: string, + ) { + const code = getSystemErrorName(err); + let details = ""; + + if (port && port > 0) { + details = ` ${address}:${port}`; + } else if (address) { + details = ` ${address}`; + } + + if (additional) { + details += ` - Local (${additional})`; + } + + // deno-lint-ignore no-explicit-any + const ex: any = new Error(`${syscall} ${code}${details}`); + ex.errno = err; + ex.code = code; + ex.syscall = syscall; + ex.address = address; + + if (port) { + ex.port = port; + } + + return captureLargerStackTrace(ex); + }, +); + +/** + * @param code A libuv error number or a c-ares error code + * @param syscall + * @param hostname + */ +export const dnsException = hideStackFrames(function (code, syscall, hostname) { + let errno; + + // If `code` is of type number, it is a libuv error number, else it is a + // c-ares error code. + if (typeof code === "number") { + errno = code; + // ENOTFOUND is not a proper POSIX error, but this error has been in place + // long enough that it's not practical to remove it. + if ( + code === codeMap.get("EAI_NODATA") || + code === codeMap.get("EAI_NONAME") + ) { + code = "ENOTFOUND"; // Fabricated error name. + } else { + code = getSystemErrorName(code); + } + } + + const message = `${syscall} ${code}${hostname ? ` ${hostname}` : ""}`; + + // deno-lint-ignore no-explicit-any + const ex: any = new Error(message); + ex.errno = errno; + ex.code = code; + ex.syscall = syscall; + + if (hostname) { + ex.hostname = hostname; + } + + return captureLargerStackTrace(ex); +}); + +/** + * All error instances in Node have additional methods and properties + * This export class is meant to be extended by these instances abstracting native JS error instances + */ +export class NodeErrorAbstraction extends Error { + code: string; + + constructor(name: string, code: string, message: string) { + super(message); + this.code = code; + this.name = name; + //This number changes depending on the name of this class + //20 characters as of now + this.stack = this.stack && `${name} [${this.code}]${this.stack.slice(20)}`; + } + + override toString() { + return `${this.name} [${this.code}]: ${this.message}`; + } +} + +export class NodeError extends NodeErrorAbstraction { + constructor(code: string, message: string) { + super(Error.prototype.name, code, message); + } +} + +export class NodeSyntaxError extends NodeErrorAbstraction + implements SyntaxError { + constructor(code: string, message: string) { + super(SyntaxError.prototype.name, code, message); + Object.setPrototypeOf(this, SyntaxError.prototype); + this.toString = function () { + return `${this.name} [${this.code}]: ${this.message}`; + }; + } +} + +export class NodeRangeError extends NodeErrorAbstraction { + constructor(code: string, message: string) { + super(RangeError.prototype.name, code, message); + Object.setPrototypeOf(this, RangeError.prototype); + this.toString = function () { + return `${this.name} [${this.code}]: ${this.message}`; + }; + } +} + +export class NodeTypeError extends NodeErrorAbstraction implements TypeError { + constructor(code: string, message: string) { + super(TypeError.prototype.name, code, message); + Object.setPrototypeOf(this, TypeError.prototype); + this.toString = function () { + return `${this.name} [${this.code}]: ${this.message}`; + }; + } +} + +export class NodeURIError extends NodeErrorAbstraction implements URIError { + constructor(code: string, message: string) { + super(URIError.prototype.name, code, message); + Object.setPrototypeOf(this, URIError.prototype); + this.toString = function () { + return `${this.name} [${this.code}]: ${this.message}`; + }; + } +} + +export interface NodeSystemErrorCtx { + code: string; + syscall: string; + message: string; + errno: number; + path?: string; + dest?: string; +} +// A specialized Error that includes an additional info property with +// additional information about the error condition. +// It has the properties present in a UVException but with a custom error +// message followed by the uv error code and uv error message. +// It also has its own error code with the original uv error context put into +// `err.info`. +// The context passed into this error must have .code, .syscall and .message, +// and may have .path and .dest. +class NodeSystemError extends NodeErrorAbstraction { + constructor(key: string, context: NodeSystemErrorCtx, msgPrefix: string) { + let message = `${msgPrefix}: ${context.syscall} returned ` + + `${context.code} (${context.message})`; + + if (context.path !== undefined) { + message += ` ${context.path}`; + } + if (context.dest !== undefined) { + message += ` => ${context.dest}`; + } + + super("SystemError", key, message); + + captureLargerStackTrace(this); + + Object.defineProperties(this, { + [kIsNodeError]: { + value: true, + enumerable: false, + writable: false, + configurable: true, + }, + info: { + value: context, + enumerable: true, + configurable: true, + writable: false, + }, + errno: { + get() { + return context.errno; + }, + set: (value) => { + context.errno = value; + }, + enumerable: true, + configurable: true, + }, + syscall: { + get() { + return context.syscall; + }, + set: (value) => { + context.syscall = value; + }, + enumerable: true, + configurable: true, + }, + }); + + if (context.path !== undefined) { + Object.defineProperty(this, "path", { + get() { + return context.path; + }, + set: (value) => { + context.path = value; + }, + enumerable: true, + configurable: true, + }); + } + + if (context.dest !== undefined) { + Object.defineProperty(this, "dest", { + get() { + return context.dest; + }, + set: (value) => { + context.dest = value; + }, + enumerable: true, + configurable: true, + }); + } + } + + override toString() { + return `${this.name} [${this.code}]: ${this.message}`; + } +} + +function makeSystemErrorWithCode(key: string, msgPrfix: string) { + return class NodeError extends NodeSystemError { + constructor(ctx: NodeSystemErrorCtx) { + super(key, ctx, msgPrfix); + } + }; +} + +export const ERR_FS_EISDIR = makeSystemErrorWithCode( + "ERR_FS_EISDIR", + "Path is a directory", +); + +function createInvalidArgType( + name: string, + expected: string | string[], +): string { + // https://github.com/nodejs/node/blob/f3eb224/lib/internal/errors.js#L1037-L1087 + expected = Array.isArray(expected) ? expected : [expected]; + let msg = "The "; + if (name.endsWith(" argument")) { + // For cases like 'first argument' + msg += `${name} `; + } else { + const type = name.includes(".") ? "property" : "argument"; + msg += `"${name}" ${type} `; + } + msg += "must be "; + + const types = []; + const instances = []; + const other = []; + for (const value of expected) { + if (kTypes.includes(value)) { + types.push(value.toLocaleLowerCase()); + } else if (classRegExp.test(value)) { + instances.push(value); + } else { + other.push(value); + } + } + + // Special handle `object` in case other instances are allowed to outline + // the differences between each other. + if (instances.length > 0) { + const pos = types.indexOf("object"); + if (pos !== -1) { + types.splice(pos, 1); + instances.push("Object"); + } + } + + if (types.length > 0) { + if (types.length > 2) { + const last = types.pop(); + msg += `one of type ${types.join(", ")}, or ${last}`; + } else if (types.length === 2) { + msg += `one of type ${types[0]} or ${types[1]}`; + } else { + msg += `of type ${types[0]}`; + } + if (instances.length > 0 || other.length > 0) { + msg += " or "; + } + } + + if (instances.length > 0) { + if (instances.length > 2) { + const last = instances.pop(); + msg += `an instance of ${instances.join(", ")}, or ${last}`; + } else { + msg += `an instance of ${instances[0]}`; + if (instances.length === 2) { + msg += ` or ${instances[1]}`; + } + } + if (other.length > 0) { + msg += " or "; + } + } + + if (other.length > 0) { + if (other.length > 2) { + const last = other.pop(); + msg += `one of ${other.join(", ")}, or ${last}`; + } else if (other.length === 2) { + msg += `one of ${other[0]} or ${other[1]}`; + } else { + if (other[0].toLowerCase() !== other[0]) { + msg += "an "; + } + msg += `${other[0]}`; + } + } + + return msg; +} + +export class ERR_INVALID_ARG_TYPE_RANGE extends NodeRangeError { + constructor(name: string, expected: string | string[], actual: unknown) { + const msg = createInvalidArgType(name, expected); + + super("ERR_INVALID_ARG_TYPE", `${msg}.${invalidArgTypeHelper(actual)}`); + } +} + +export class ERR_INVALID_ARG_TYPE extends NodeTypeError { + constructor(name: string, expected: string | string[], actual: unknown) { + const msg = createInvalidArgType(name, expected); + + super("ERR_INVALID_ARG_TYPE", `${msg}.${invalidArgTypeHelper(actual)}`); + } + + static RangeError = ERR_INVALID_ARG_TYPE_RANGE; +} + +export class ERR_INVALID_ARG_VALUE_RANGE extends NodeRangeError { + constructor(name: string, value: unknown, reason: string = "is invalid") { + const type = name.includes(".") ? "property" : "argument"; + const inspected = inspect(value); + + super( + "ERR_INVALID_ARG_VALUE", + `The ${type} '${name}' ${reason}. Received ${inspected}`, + ); + } +} + +export class ERR_INVALID_ARG_VALUE extends NodeTypeError { + constructor(name: string, value: unknown, reason: string = "is invalid") { + const type = name.includes(".") ? "property" : "argument"; + const inspected = inspect(value); + + super( + "ERR_INVALID_ARG_VALUE", + `The ${type} '${name}' ${reason}. Received ${inspected}`, + ); + } + + static RangeError = ERR_INVALID_ARG_VALUE_RANGE; +} + +// A helper function to simplify checking for ERR_INVALID_ARG_TYPE output. +// deno-lint-ignore no-explicit-any +function invalidArgTypeHelper(input: any) { + if (input == null) { + return ` Received ${input}`; + } + if (typeof input === "function" && input.name) { + return ` Received function ${input.name}`; + } + if (typeof input === "object") { + if (input.constructor && input.constructor.name) { + return ` Received an instance of ${input.constructor.name}`; + } + return ` Received ${inspect(input, { depth: -1 })}`; + } + let inspected = inspect(input, { colors: false }); + if (inspected.length > 25) { + inspected = `${inspected.slice(0, 25)}...`; + } + return ` Received type ${typeof input} (${inspected})`; +} + +export class ERR_OUT_OF_RANGE extends RangeError { + code = "ERR_OUT_OF_RANGE"; + + constructor( + str: string, + range: string, + input: unknown, + replaceDefaultBoolean = false, + ) { + assert(range, 'Missing "range" argument'); + let msg = replaceDefaultBoolean + ? str + : `The value of "${str}" is out of range.`; + let received; + if (Number.isInteger(input) && Math.abs(input as number) > 2 ** 32) { + received = addNumericalSeparator(String(input)); + } else if (typeof input === "bigint") { + received = String(input); + if (input > 2n ** 32n || input < -(2n ** 32n)) { + received = addNumericalSeparator(received); + } + received += "n"; + } else { + received = inspect(input); + } + msg += ` It must be ${range}. Received ${received}`; + + super(msg); + + const { name } = this; + // Add the error code to the name to include it in the stack trace. + this.name = `${name} [${this.code}]`; + // Access the stack to generate the error message including the error code from the name. + this.stack; + // Reset the name to the actual name. + this.name = name; + } +} + +export class ERR_AMBIGUOUS_ARGUMENT extends NodeTypeError { + constructor(x: string, y: string) { + super("ERR_AMBIGUOUS_ARGUMENT", `The "${x}" argument is ambiguous. ${y}`); + } +} + +export class ERR_ARG_NOT_ITERABLE extends NodeTypeError { + constructor(x: string) { + super("ERR_ARG_NOT_ITERABLE", `${x} must be iterable`); + } +} + +export class ERR_ASSERTION extends NodeError { + constructor(x: string) { + super("ERR_ASSERTION", `${x}`); + } +} + +export class ERR_ASYNC_CALLBACK extends NodeTypeError { + constructor(x: string) { + super("ERR_ASYNC_CALLBACK", `${x} must be a function`); + } +} + +export class ERR_ASYNC_TYPE extends NodeTypeError { + constructor(x: string) { + super("ERR_ASYNC_TYPE", `Invalid name for async "type": ${x}`); + } +} + +export class ERR_BROTLI_INVALID_PARAM extends NodeRangeError { + constructor(x: string) { + super("ERR_BROTLI_INVALID_PARAM", `${x} is not a valid Brotli parameter`); + } +} + +export class ERR_BUFFER_OUT_OF_BOUNDS extends NodeRangeError { + constructor(name?: string) { + super( + "ERR_BUFFER_OUT_OF_BOUNDS", + name + ? `"${name}" is outside of buffer bounds` + : "Attempt to access memory outside buffer bounds", + ); + } +} + +export class ERR_BUFFER_TOO_LARGE extends NodeRangeError { + constructor(x: string) { + super( + "ERR_BUFFER_TOO_LARGE", + `Cannot create a Buffer larger than ${x} bytes`, + ); + } +} + +export class ERR_CANNOT_WATCH_SIGINT extends NodeError { + constructor() { + super("ERR_CANNOT_WATCH_SIGINT", "Cannot watch for SIGINT signals"); + } +} + +export class ERR_CHILD_CLOSED_BEFORE_REPLY extends NodeError { + constructor() { + super( + "ERR_CHILD_CLOSED_BEFORE_REPLY", + "Child closed before reply received", + ); + } +} + +export class ERR_CHILD_PROCESS_IPC_REQUIRED extends NodeError { + constructor(x: string) { + super( + "ERR_CHILD_PROCESS_IPC_REQUIRED", + `Forked processes must have an IPC channel, missing value 'ipc' in ${x}`, + ); + } +} + +export class ERR_CHILD_PROCESS_STDIO_MAXBUFFER extends NodeRangeError { + constructor(x: string) { + super( + "ERR_CHILD_PROCESS_STDIO_MAXBUFFER", + `${x} maxBuffer length exceeded`, + ); + } +} + +export class ERR_CONSOLE_WRITABLE_STREAM extends NodeTypeError { + constructor(x: string) { + super( + "ERR_CONSOLE_WRITABLE_STREAM", + `Console expects a writable stream instance for ${x}`, + ); + } +} + +export class ERR_CONTEXT_NOT_INITIALIZED extends NodeError { + constructor() { + super("ERR_CONTEXT_NOT_INITIALIZED", "context used is not initialized"); + } +} + +export class ERR_CPU_USAGE extends NodeError { + constructor(x: string) { + super("ERR_CPU_USAGE", `Unable to obtain cpu usage ${x}`); + } +} + +export class ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED extends NodeError { + constructor() { + super( + "ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED", + "Custom engines not supported by this OpenSSL", + ); + } +} + +export class ERR_CRYPTO_ECDH_INVALID_FORMAT extends NodeTypeError { + constructor(x: string) { + super("ERR_CRYPTO_ECDH_INVALID_FORMAT", `Invalid ECDH format: ${x}`); + } +} + +export class ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY extends NodeError { + constructor() { + super( + "ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY", + "Public key is not valid for specified curve", + ); + } +} + +export class ERR_CRYPTO_ENGINE_UNKNOWN extends NodeError { + constructor(x: string) { + super("ERR_CRYPTO_ENGINE_UNKNOWN", `Engine "${x}" was not found`); + } +} + +export class ERR_CRYPTO_FIPS_FORCED extends NodeError { + constructor() { + super( + "ERR_CRYPTO_FIPS_FORCED", + "Cannot set FIPS mode, it was forced with --force-fips at startup.", + ); + } +} + +export class ERR_CRYPTO_FIPS_UNAVAILABLE extends NodeError { + constructor() { + super( + "ERR_CRYPTO_FIPS_UNAVAILABLE", + "Cannot set FIPS mode in a non-FIPS build.", + ); + } +} + +export class ERR_CRYPTO_HASH_FINALIZED extends NodeError { + constructor() { + super("ERR_CRYPTO_HASH_FINALIZED", "Digest already called"); + } +} + +export class ERR_CRYPTO_HASH_UPDATE_FAILED extends NodeError { + constructor() { + super("ERR_CRYPTO_HASH_UPDATE_FAILED", "Hash update failed"); + } +} + +export class ERR_CRYPTO_INCOMPATIBLE_KEY extends NodeError { + constructor(x: string, y: string) { + super("ERR_CRYPTO_INCOMPATIBLE_KEY", `Incompatible ${x}: ${y}`); + } +} + +export class ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS extends NodeError { + constructor(x: string, y: string) { + super( + "ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS", + `The selected key encoding ${x} ${y}.`, + ); + } +} + +export class ERR_CRYPTO_INVALID_DIGEST extends NodeTypeError { + constructor(x: string) { + super("ERR_CRYPTO_INVALID_DIGEST", `Invalid digest: ${x}`); + } +} + +export class ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE extends NodeTypeError { + constructor(x: string, y: string) { + super( + "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE", + `Invalid key object type ${x}, expected ${y}.`, + ); + } +} + +export class ERR_CRYPTO_INVALID_STATE extends NodeError { + constructor(x: string) { + super("ERR_CRYPTO_INVALID_STATE", `Invalid state for operation ${x}`); + } +} + +export class ERR_CRYPTO_PBKDF2_ERROR extends NodeError { + constructor() { + super("ERR_CRYPTO_PBKDF2_ERROR", "PBKDF2 error"); + } +} + +export class ERR_CRYPTO_SCRYPT_INVALID_PARAMETER extends NodeError { + constructor() { + super("ERR_CRYPTO_SCRYPT_INVALID_PARAMETER", "Invalid scrypt parameter"); + } +} + +export class ERR_CRYPTO_SCRYPT_NOT_SUPPORTED extends NodeError { + constructor() { + super("ERR_CRYPTO_SCRYPT_NOT_SUPPORTED", "Scrypt algorithm not supported"); + } +} + +export class ERR_CRYPTO_SIGN_KEY_REQUIRED extends NodeError { + constructor() { + super("ERR_CRYPTO_SIGN_KEY_REQUIRED", "No key provided to sign"); + } +} + +export class ERR_DIR_CLOSED extends NodeError { + constructor() { + super("ERR_DIR_CLOSED", "Directory handle was closed"); + } +} + +export class ERR_DIR_CONCURRENT_OPERATION extends NodeError { + constructor() { + super( + "ERR_DIR_CONCURRENT_OPERATION", + "Cannot do synchronous work on directory handle with concurrent asynchronous operations", + ); + } +} + +export class ERR_DNS_SET_SERVERS_FAILED extends NodeError { + constructor(x: string, y: string) { + super( + "ERR_DNS_SET_SERVERS_FAILED", + `c-ares failed to set servers: "${x}" [${y}]`, + ); + } +} + +export class ERR_DOMAIN_CALLBACK_NOT_AVAILABLE extends NodeError { + constructor() { + super( + "ERR_DOMAIN_CALLBACK_NOT_AVAILABLE", + "A callback was registered through " + + "process.setUncaughtExceptionCaptureCallback(), which is mutually " + + "exclusive with using the `domain` module", + ); + } +} + +export class ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE + extends NodeError { + constructor() { + super( + "ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE", + "The `domain` module is in use, which is mutually exclusive with calling " + + "process.setUncaughtExceptionCaptureCallback()", + ); + } +} + +export class ERR_ENCODING_INVALID_ENCODED_DATA extends NodeErrorAbstraction + implements TypeError { + errno: number; + constructor(encoding: string, ret: number) { + super( + TypeError.prototype.name, + "ERR_ENCODING_INVALID_ENCODED_DATA", + `The encoded data was not valid for encoding ${encoding}`, + ); + Object.setPrototypeOf(this, TypeError.prototype); + + this.errno = ret; + } +} + +export class ERR_ENCODING_NOT_SUPPORTED extends NodeRangeError { + constructor(x: string) { + super("ERR_ENCODING_NOT_SUPPORTED", `The "${x}" encoding is not supported`); + } +} +export class ERR_EVAL_ESM_CANNOT_PRINT extends NodeError { + constructor() { + super("ERR_EVAL_ESM_CANNOT_PRINT", `--print cannot be used with ESM input`); + } +} +export class ERR_EVENT_RECURSION extends NodeError { + constructor(x: string) { + super( + "ERR_EVENT_RECURSION", + `The event "${x}" is already being dispatched`, + ); + } +} +export class ERR_FEATURE_UNAVAILABLE_ON_PLATFORM extends NodeTypeError { + constructor(x: string) { + super( + "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM", + `The feature ${x} is unavailable on the current platform, which is being used to run Node.js`, + ); + } +} +export class ERR_FS_FILE_TOO_LARGE extends NodeRangeError { + constructor(x: string) { + super("ERR_FS_FILE_TOO_LARGE", `File size (${x}) is greater than 2 GB`); + } +} +export class ERR_FS_INVALID_SYMLINK_TYPE extends NodeError { + constructor(x: string) { + super( + "ERR_FS_INVALID_SYMLINK_TYPE", + `Symlink type must be one of "dir", "file", or "junction". Received "${x}"`, + ); + } +} +export class ERR_HTTP2_ALTSVC_INVALID_ORIGIN extends NodeTypeError { + constructor() { + super( + "ERR_HTTP2_ALTSVC_INVALID_ORIGIN", + `HTTP/2 ALTSVC frames require a valid origin`, + ); + } +} +export class ERR_HTTP2_ALTSVC_LENGTH extends NodeTypeError { + constructor() { + super( + "ERR_HTTP2_ALTSVC_LENGTH", + `HTTP/2 ALTSVC frames are limited to 16382 bytes`, + ); + } +} +export class ERR_HTTP2_CONNECT_AUTHORITY extends NodeError { + constructor() { + super( + "ERR_HTTP2_CONNECT_AUTHORITY", + `:authority header is required for CONNECT requests`, + ); + } +} +export class ERR_HTTP2_CONNECT_PATH extends NodeError { + constructor() { + super( + "ERR_HTTP2_CONNECT_PATH", + `The :path header is forbidden for CONNECT requests`, + ); + } +} +export class ERR_HTTP2_CONNECT_SCHEME extends NodeError { + constructor() { + super( + "ERR_HTTP2_CONNECT_SCHEME", + `The :scheme header is forbidden for CONNECT requests`, + ); + } +} +export class ERR_HTTP2_GOAWAY_SESSION extends NodeError { + constructor() { + super( + "ERR_HTTP2_GOAWAY_SESSION", + `New streams cannot be created after receiving a GOAWAY`, + ); + } +} +export class ERR_HTTP2_HEADERS_AFTER_RESPOND extends NodeError { + constructor() { + super( + "ERR_HTTP2_HEADERS_AFTER_RESPOND", + `Cannot specify additional headers after response initiated`, + ); + } +} +export class ERR_HTTP2_HEADERS_SENT extends NodeError { + constructor() { + super("ERR_HTTP2_HEADERS_SENT", `Response has already been initiated.`); + } +} +export class ERR_HTTP2_HEADER_SINGLE_VALUE extends NodeTypeError { + constructor(x: string) { + super( + "ERR_HTTP2_HEADER_SINGLE_VALUE", + `Header field "${x}" must only have a single value`, + ); + } +} +export class ERR_HTTP2_INFO_STATUS_NOT_ALLOWED extends NodeRangeError { + constructor() { + super( + "ERR_HTTP2_INFO_STATUS_NOT_ALLOWED", + `Informational status codes cannot be used`, + ); + } +} +export class ERR_HTTP2_INVALID_CONNECTION_HEADERS extends NodeTypeError { + constructor(x: string) { + super( + "ERR_HTTP2_INVALID_CONNECTION_HEADERS", + `HTTP/1 Connection specific headers are forbidden: "${x}"`, + ); + } +} +export class ERR_HTTP2_INVALID_HEADER_VALUE extends NodeTypeError { + constructor(x: string, y: string) { + super( + "ERR_HTTP2_INVALID_HEADER_VALUE", + `Invalid value "${x}" for header "${y}"`, + ); + } +} +export class ERR_HTTP2_INVALID_INFO_STATUS extends NodeRangeError { + constructor(x: string) { + super( + "ERR_HTTP2_INVALID_INFO_STATUS", + `Invalid informational status code: ${x}`, + ); + } +} +export class ERR_HTTP2_INVALID_ORIGIN extends NodeTypeError { + constructor() { + super( + "ERR_HTTP2_INVALID_ORIGIN", + `HTTP/2 ORIGIN frames require a valid origin`, + ); + } +} +export class ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH extends NodeRangeError { + constructor() { + super( + "ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH", + `Packed settings length must be a multiple of six`, + ); + } +} +export class ERR_HTTP2_INVALID_PSEUDOHEADER extends NodeTypeError { + constructor(x: string) { + super( + "ERR_HTTP2_INVALID_PSEUDOHEADER", + `"${x}" is an invalid pseudoheader or is used incorrectly`, + ); + } +} +export class ERR_HTTP2_INVALID_SESSION extends NodeError { + constructor() { + super("ERR_HTTP2_INVALID_SESSION", `The session has been destroyed`); + } +} +export class ERR_HTTP2_INVALID_STREAM extends NodeError { + constructor() { + super("ERR_HTTP2_INVALID_STREAM", `The stream has been destroyed`); + } +} +export class ERR_HTTP2_MAX_PENDING_SETTINGS_ACK extends NodeError { + constructor() { + super( + "ERR_HTTP2_MAX_PENDING_SETTINGS_ACK", + `Maximum number of pending settings acknowledgements`, + ); + } +} +export class ERR_HTTP2_NESTED_PUSH extends NodeError { + constructor() { + super( + "ERR_HTTP2_NESTED_PUSH", + `A push stream cannot initiate another push stream.`, + ); + } +} +export class ERR_HTTP2_NO_SOCKET_MANIPULATION extends NodeError { + constructor() { + super( + "ERR_HTTP2_NO_SOCKET_MANIPULATION", + `HTTP/2 sockets should not be directly manipulated (e.g. read and written)`, + ); + } +} +export class ERR_HTTP2_ORIGIN_LENGTH extends NodeTypeError { + constructor() { + super( + "ERR_HTTP2_ORIGIN_LENGTH", + `HTTP/2 ORIGIN frames are limited to 16382 bytes`, + ); + } +} +export class ERR_HTTP2_OUT_OF_STREAMS extends NodeError { + constructor() { + super( + "ERR_HTTP2_OUT_OF_STREAMS", + `No stream ID is available because maximum stream ID has been reached`, + ); + } +} +export class ERR_HTTP2_PAYLOAD_FORBIDDEN extends NodeError { + constructor(x: string) { + super( + "ERR_HTTP2_PAYLOAD_FORBIDDEN", + `Responses with ${x} status must not have a payload`, + ); + } +} +export class ERR_HTTP2_PING_CANCEL extends NodeError { + constructor() { + super("ERR_HTTP2_PING_CANCEL", `HTTP2 ping cancelled`); + } +} +export class ERR_HTTP2_PING_LENGTH extends NodeRangeError { + constructor() { + super("ERR_HTTP2_PING_LENGTH", `HTTP2 ping payload must be 8 bytes`); + } +} +export class ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED extends NodeTypeError { + constructor() { + super( + "ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED", + `Cannot set HTTP/2 pseudo-headers`, + ); + } +} +export class ERR_HTTP2_PUSH_DISABLED extends NodeError { + constructor() { + super("ERR_HTTP2_PUSH_DISABLED", `HTTP/2 client has disabled push streams`); + } +} +export class ERR_HTTP2_SEND_FILE extends NodeError { + constructor() { + super("ERR_HTTP2_SEND_FILE", `Directories cannot be sent`); + } +} +export class ERR_HTTP2_SEND_FILE_NOSEEK extends NodeError { + constructor() { + super( + "ERR_HTTP2_SEND_FILE_NOSEEK", + `Offset or length can only be specified for regular files`, + ); + } +} +export class ERR_HTTP2_SESSION_ERROR extends NodeError { + constructor(x: string) { + super("ERR_HTTP2_SESSION_ERROR", `Session closed with error code ${x}`); + } +} +export class ERR_HTTP2_SETTINGS_CANCEL extends NodeError { + constructor() { + super("ERR_HTTP2_SETTINGS_CANCEL", `HTTP2 session settings canceled`); + } +} +export class ERR_HTTP2_SOCKET_BOUND extends NodeError { + constructor() { + super( + "ERR_HTTP2_SOCKET_BOUND", + `The socket is already bound to an Http2Session`, + ); + } +} +export class ERR_HTTP2_SOCKET_UNBOUND extends NodeError { + constructor() { + super( + "ERR_HTTP2_SOCKET_UNBOUND", + `The socket has been disconnected from the Http2Session`, + ); + } +} +export class ERR_HTTP2_STATUS_101 extends NodeError { + constructor() { + super( + "ERR_HTTP2_STATUS_101", + `HTTP status code 101 (Switching Protocols) is forbidden in HTTP/2`, + ); + } +} +export class ERR_HTTP2_STATUS_INVALID extends NodeRangeError { + constructor(x: string) { + super("ERR_HTTP2_STATUS_INVALID", `Invalid status code: ${x}`); + } +} +export class ERR_HTTP2_STREAM_ERROR extends NodeError { + constructor(x: string) { + super("ERR_HTTP2_STREAM_ERROR", `Stream closed with error code ${x}`); + } +} +export class ERR_HTTP2_STREAM_SELF_DEPENDENCY extends NodeError { + constructor() { + super( + "ERR_HTTP2_STREAM_SELF_DEPENDENCY", + `A stream cannot depend on itself`, + ); + } +} +export class ERR_HTTP2_TRAILERS_ALREADY_SENT extends NodeError { + constructor() { + super( + "ERR_HTTP2_TRAILERS_ALREADY_SENT", + `Trailing headers have already been sent`, + ); + } +} +export class ERR_HTTP2_TRAILERS_NOT_READY extends NodeError { + constructor() { + super( + "ERR_HTTP2_TRAILERS_NOT_READY", + `Trailing headers cannot be sent until after the wantTrailers event is emitted`, + ); + } +} +export class ERR_HTTP2_UNSUPPORTED_PROTOCOL extends NodeError { + constructor(x: string) { + super("ERR_HTTP2_UNSUPPORTED_PROTOCOL", `protocol "${x}" is unsupported.`); + } +} +export class ERR_HTTP_HEADERS_SENT extends NodeError { + constructor(x: string) { + super( + "ERR_HTTP_HEADERS_SENT", + `Cannot ${x} headers after they are sent to the client`, + ); + } +} +export class ERR_HTTP_INVALID_HEADER_VALUE extends NodeTypeError { + constructor(x: string, y: string) { + super( + "ERR_HTTP_INVALID_HEADER_VALUE", + `Invalid value "${x}" for header "${y}"`, + ); + } +} +export class ERR_HTTP_INVALID_STATUS_CODE extends NodeRangeError { + constructor(x: string) { + super("ERR_HTTP_INVALID_STATUS_CODE", `Invalid status code: ${x}`); + } +} +export class ERR_HTTP_SOCKET_ENCODING extends NodeError { + constructor() { + super( + "ERR_HTTP_SOCKET_ENCODING", + `Changing the socket encoding is not allowed per RFC7230 Section 3.`, + ); + } +} +export class ERR_HTTP_TRAILER_INVALID extends NodeError { + constructor() { + super( + "ERR_HTTP_TRAILER_INVALID", + `Trailers are invalid with this transfer encoding`, + ); + } +} +export class ERR_INCOMPATIBLE_OPTION_PAIR extends NodeTypeError { + constructor(x: string, y: string) { + super( + "ERR_INCOMPATIBLE_OPTION_PAIR", + `Option "${x}" cannot be used in combination with option "${y}"`, + ); + } +} +export class ERR_INPUT_TYPE_NOT_ALLOWED extends NodeError { + constructor() { + super( + "ERR_INPUT_TYPE_NOT_ALLOWED", + `--input-type can only be used with string input via --eval, --print, or STDIN`, + ); + } +} +export class ERR_INSPECTOR_ALREADY_ACTIVATED extends NodeError { + constructor() { + super( + "ERR_INSPECTOR_ALREADY_ACTIVATED", + `Inspector is already activated. Close it with inspector.close() before activating it again.`, + ); + } +} +export class ERR_INSPECTOR_ALREADY_CONNECTED extends NodeError { + constructor(x: string) { + super("ERR_INSPECTOR_ALREADY_CONNECTED", `${x} is already connected`); + } +} +export class ERR_INSPECTOR_CLOSED extends NodeError { + constructor() { + super("ERR_INSPECTOR_CLOSED", `Session was closed`); + } +} +export class ERR_INSPECTOR_COMMAND extends NodeError { + constructor(x: number, y: string) { + super("ERR_INSPECTOR_COMMAND", `Inspector error ${x}: ${y}`); + } +} +export class ERR_INSPECTOR_NOT_ACTIVE extends NodeError { + constructor() { + super("ERR_INSPECTOR_NOT_ACTIVE", `Inspector is not active`); + } +} +export class ERR_INSPECTOR_NOT_AVAILABLE extends NodeError { + constructor() { + super("ERR_INSPECTOR_NOT_AVAILABLE", `Inspector is not available`); + } +} +export class ERR_INSPECTOR_NOT_CONNECTED extends NodeError { + constructor() { + super("ERR_INSPECTOR_NOT_CONNECTED", `Session is not connected`); + } +} +export class ERR_INSPECTOR_NOT_WORKER extends NodeError { + constructor() { + super("ERR_INSPECTOR_NOT_WORKER", `Current thread is not a worker`); + } +} +export class ERR_INVALID_ASYNC_ID extends NodeRangeError { + constructor(x: string, y: string | number) { + super("ERR_INVALID_ASYNC_ID", `Invalid ${x} value: ${y}`); + } +} +export class ERR_INVALID_BUFFER_SIZE extends NodeRangeError { + constructor(x: string) { + super("ERR_INVALID_BUFFER_SIZE", `Buffer size must be a multiple of ${x}`); + } +} +export class ERR_INVALID_CURSOR_POS extends NodeTypeError { + constructor() { + super( + "ERR_INVALID_CURSOR_POS", + `Cannot set cursor row without setting its column`, + ); + } +} +export class ERR_INVALID_FD extends NodeRangeError { + constructor(x: string) { + super("ERR_INVALID_FD", `"fd" must be a positive integer: ${x}`); + } +} +export class ERR_INVALID_FD_TYPE extends NodeTypeError { + constructor(x: string) { + super("ERR_INVALID_FD_TYPE", `Unsupported fd type: ${x}`); + } +} +export class ERR_INVALID_FILE_URL_HOST extends NodeTypeError { + constructor(x: string) { + super( + "ERR_INVALID_FILE_URL_HOST", + `File URL host must be "localhost" or empty on ${x}`, + ); + } +} +export class ERR_INVALID_FILE_URL_PATH extends NodeTypeError { + constructor(x: string) { + super("ERR_INVALID_FILE_URL_PATH", `File URL path ${x}`); + } +} +export class ERR_INVALID_HANDLE_TYPE extends NodeTypeError { + constructor() { + super("ERR_INVALID_HANDLE_TYPE", `This handle type cannot be sent`); + } +} +export class ERR_INVALID_HTTP_TOKEN extends NodeTypeError { + constructor(x: string, y: string) { + super("ERR_INVALID_HTTP_TOKEN", `${x} must be a valid HTTP token ["${y}"]`); + } +} +export class ERR_INVALID_IP_ADDRESS extends NodeTypeError { + constructor(x: string) { + super("ERR_INVALID_IP_ADDRESS", `Invalid IP address: ${x}`); + } +} +export class ERR_INVALID_OPT_VALUE_ENCODING extends NodeTypeError { + constructor(x: string) { + super( + "ERR_INVALID_OPT_VALUE_ENCODING", + `The value "${x}" is invalid for option "encoding"`, + ); + } +} +export class ERR_INVALID_PERFORMANCE_MARK extends NodeError { + constructor(x: string) { + super( + "ERR_INVALID_PERFORMANCE_MARK", + `The "${x}" performance mark has not been set`, + ); + } +} +export class ERR_INVALID_PROTOCOL extends NodeTypeError { + constructor(x: string, y: string) { + super( + "ERR_INVALID_PROTOCOL", + `Protocol "${x}" not supported. Expected "${y}"`, + ); + } +} +export class ERR_INVALID_REPL_EVAL_CONFIG extends NodeTypeError { + constructor() { + super( + "ERR_INVALID_REPL_EVAL_CONFIG", + `Cannot specify both "breakEvalOnSigint" and "eval" for REPL`, + ); + } +} +export class ERR_INVALID_REPL_INPUT extends NodeTypeError { + constructor(x: string) { + super("ERR_INVALID_REPL_INPUT", `${x}`); + } +} +export class ERR_INVALID_SYNC_FORK_INPUT extends NodeTypeError { + constructor(x: string) { + super( + "ERR_INVALID_SYNC_FORK_INPUT", + `Asynchronous forks do not support Buffer, TypedArray, DataView or string input: ${x}`, + ); + } +} +export class ERR_INVALID_THIS extends NodeTypeError { + constructor(x: string) { + super("ERR_INVALID_THIS", `Value of "this" must be of type ${x}`); + } +} +export class ERR_INVALID_TUPLE extends NodeTypeError { + constructor(x: string, y: string) { + super("ERR_INVALID_TUPLE", `${x} must be an iterable ${y} tuple`); + } +} +export class ERR_INVALID_URI extends NodeURIError { + constructor() { + super("ERR_INVALID_URI", `URI malformed`); + } +} +export class ERR_IPC_CHANNEL_CLOSED extends NodeError { + constructor() { + super("ERR_IPC_CHANNEL_CLOSED", `Channel closed`); + } +} +export class ERR_IPC_DISCONNECTED extends NodeError { + constructor() { + super("ERR_IPC_DISCONNECTED", `IPC channel is already disconnected`); + } +} +export class ERR_IPC_ONE_PIPE extends NodeError { + constructor() { + super("ERR_IPC_ONE_PIPE", `Child process can have only one IPC pipe`); + } +} +export class ERR_IPC_SYNC_FORK extends NodeError { + constructor() { + super("ERR_IPC_SYNC_FORK", `IPC cannot be used with synchronous forks`); + } +} +export class ERR_MANIFEST_DEPENDENCY_MISSING extends NodeError { + constructor(x: string, y: string) { + super( + "ERR_MANIFEST_DEPENDENCY_MISSING", + `Manifest resource ${x} does not list ${y} as a dependency specifier`, + ); + } +} +export class ERR_MANIFEST_INTEGRITY_MISMATCH extends NodeSyntaxError { + constructor(x: string) { + super( + "ERR_MANIFEST_INTEGRITY_MISMATCH", + `Manifest resource ${x} has multiple entries but integrity lists do not match`, + ); + } +} +export class ERR_MANIFEST_INVALID_RESOURCE_FIELD extends NodeTypeError { + constructor(x: string, y: string) { + super( + "ERR_MANIFEST_INVALID_RESOURCE_FIELD", + `Manifest resource ${x} has invalid property value for ${y}`, + ); + } +} +export class ERR_MANIFEST_TDZ extends NodeError { + constructor() { + super("ERR_MANIFEST_TDZ", `Manifest initialization has not yet run`); + } +} +export class ERR_MANIFEST_UNKNOWN_ONERROR extends NodeSyntaxError { + constructor(x: string) { + super( + "ERR_MANIFEST_UNKNOWN_ONERROR", + `Manifest specified unknown error behavior "${x}".`, + ); + } +} +export class ERR_METHOD_NOT_IMPLEMENTED extends NodeError { + constructor(x: string) { + super("ERR_METHOD_NOT_IMPLEMENTED", `The ${x} method is not implemented`); + } +} +export class ERR_MISSING_ARGS extends NodeTypeError { + constructor(...args: (string | string[])[]) { + let msg = "The "; + + const len = args.length; + + const wrap = (a: unknown) => `"${a}"`; + + args = args.map((a) => + Array.isArray(a) ? a.map(wrap).join(" or ") : wrap(a) + ); + + switch (len) { + case 1: + msg += `${args[0]} argument`; + break; + case 2: + msg += `${args[0]} and ${args[1]} arguments`; + break; + default: + msg += args.slice(0, len - 1).join(", "); + msg += `, and ${args[len - 1]} arguments`; + break; + } + + super("ERR_MISSING_ARGS", `${msg} must be specified`); + } +} +export class ERR_MISSING_OPTION extends NodeTypeError { + constructor(x: string) { + super("ERR_MISSING_OPTION", `${x} is required`); + } +} +export class ERR_MULTIPLE_CALLBACK extends NodeError { + constructor() { + super("ERR_MULTIPLE_CALLBACK", `Callback called multiple times`); + } +} +export class ERR_NAPI_CONS_FUNCTION extends NodeTypeError { + constructor() { + super("ERR_NAPI_CONS_FUNCTION", `Constructor must be a function`); + } +} +export class ERR_NAPI_INVALID_DATAVIEW_ARGS extends NodeRangeError { + constructor() { + super( + "ERR_NAPI_INVALID_DATAVIEW_ARGS", + `byte_offset + byte_length should be less than or equal to the size in bytes of the array passed in`, + ); + } +} +export class ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT extends NodeRangeError { + constructor(x: string, y: string) { + super( + "ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT", + `start offset of ${x} should be a multiple of ${y}`, + ); + } +} +export class ERR_NAPI_INVALID_TYPEDARRAY_LENGTH extends NodeRangeError { + constructor() { + super("ERR_NAPI_INVALID_TYPEDARRAY_LENGTH", `Invalid typed array length`); + } +} +export class ERR_NO_CRYPTO extends NodeError { + constructor() { + super( + "ERR_NO_CRYPTO", + `Node.js is not compiled with OpenSSL crypto support`, + ); + } +} +export class ERR_NO_ICU extends NodeTypeError { + constructor(x: string) { + super( + "ERR_NO_ICU", + `${x} is not supported on Node.js compiled without ICU`, + ); + } +} +export class ERR_QUICCLIENTSESSION_FAILED extends NodeError { + constructor(x: string) { + super( + "ERR_QUICCLIENTSESSION_FAILED", + `Failed to create a new QuicClientSession: ${x}`, + ); + } +} +export class ERR_QUICCLIENTSESSION_FAILED_SETSOCKET extends NodeError { + constructor() { + super( + "ERR_QUICCLIENTSESSION_FAILED_SETSOCKET", + `Failed to set the QuicSocket`, + ); + } +} +export class ERR_QUICSESSION_DESTROYED extends NodeError { + constructor(x: string) { + super( + "ERR_QUICSESSION_DESTROYED", + `Cannot call ${x} after a QuicSession has been destroyed`, + ); + } +} +export class ERR_QUICSESSION_INVALID_DCID extends NodeError { + constructor(x: string) { + super("ERR_QUICSESSION_INVALID_DCID", `Invalid DCID value: ${x}`); + } +} +export class ERR_QUICSESSION_UPDATEKEY extends NodeError { + constructor() { + super("ERR_QUICSESSION_UPDATEKEY", `Unable to update QuicSession keys`); + } +} +export class ERR_QUICSOCKET_DESTROYED extends NodeError { + constructor(x: string) { + super( + "ERR_QUICSOCKET_DESTROYED", + `Cannot call ${x} after a QuicSocket has been destroyed`, + ); + } +} +export class ERR_QUICSOCKET_INVALID_STATELESS_RESET_SECRET_LENGTH + extends NodeError { + constructor() { + super( + "ERR_QUICSOCKET_INVALID_STATELESS_RESET_SECRET_LENGTH", + `The stateResetToken must be exactly 16-bytes in length`, + ); + } +} +export class ERR_QUICSOCKET_LISTENING extends NodeError { + constructor() { + super("ERR_QUICSOCKET_LISTENING", `This QuicSocket is already listening`); + } +} +export class ERR_QUICSOCKET_UNBOUND extends NodeError { + constructor(x: string) { + super( + "ERR_QUICSOCKET_UNBOUND", + `Cannot call ${x} before a QuicSocket has been bound`, + ); + } +} +export class ERR_QUICSTREAM_DESTROYED extends NodeError { + constructor(x: string) { + super( + "ERR_QUICSTREAM_DESTROYED", + `Cannot call ${x} after a QuicStream has been destroyed`, + ); + } +} +export class ERR_QUICSTREAM_INVALID_PUSH extends NodeError { + constructor() { + super( + "ERR_QUICSTREAM_INVALID_PUSH", + `Push streams are only supported on client-initiated, bidirectional streams`, + ); + } +} +export class ERR_QUICSTREAM_OPEN_FAILED extends NodeError { + constructor() { + super("ERR_QUICSTREAM_OPEN_FAILED", `Opening a new QuicStream failed`); + } +} +export class ERR_QUICSTREAM_UNSUPPORTED_PUSH extends NodeError { + constructor() { + super( + "ERR_QUICSTREAM_UNSUPPORTED_PUSH", + `Push streams are not supported on this QuicSession`, + ); + } +} +export class ERR_QUIC_TLS13_REQUIRED extends NodeError { + constructor() { + super("ERR_QUIC_TLS13_REQUIRED", `QUIC requires TLS version 1.3`); + } +} +export class ERR_SCRIPT_EXECUTION_INTERRUPTED extends NodeError { + constructor() { + super( + "ERR_SCRIPT_EXECUTION_INTERRUPTED", + "Script execution was interrupted by `SIGINT`", + ); + } +} +export class ERR_SERVER_ALREADY_LISTEN extends NodeError { + constructor() { + super( + "ERR_SERVER_ALREADY_LISTEN", + `Listen method has been called more than once without closing.`, + ); + } +} +export class ERR_SERVER_NOT_RUNNING extends NodeError { + constructor() { + super("ERR_SERVER_NOT_RUNNING", `Server is not running.`); + } +} +export class ERR_SOCKET_ALREADY_BOUND extends NodeError { + constructor() { + super("ERR_SOCKET_ALREADY_BOUND", `Socket is already bound`); + } +} +export class ERR_SOCKET_BAD_BUFFER_SIZE extends NodeTypeError { + constructor() { + super( + "ERR_SOCKET_BAD_BUFFER_SIZE", + `Buffer size must be a positive integer`, + ); + } +} +export class ERR_SOCKET_BAD_PORT extends NodeRangeError { + constructor(name: string, port: unknown, allowZero = true) { + assert( + typeof allowZero === "boolean", + "The 'allowZero' argument must be of type boolean.", + ); + + const operator = allowZero ? ">=" : ">"; + + super( + "ERR_SOCKET_BAD_PORT", + `${name} should be ${operator} 0 and < 65536. Received ${port}.`, + ); + } +} +export class ERR_SOCKET_BAD_TYPE extends NodeTypeError { + constructor() { + super( + "ERR_SOCKET_BAD_TYPE", + `Bad socket type specified. Valid types are: udp4, udp6`, + ); + } +} +export class ERR_SOCKET_BUFFER_SIZE extends NodeSystemError { + constructor(ctx: NodeSystemErrorCtx) { + super("ERR_SOCKET_BUFFER_SIZE", ctx, "Could not get or set buffer size"); + } +} +export class ERR_SOCKET_CLOSED extends NodeError { + constructor() { + super("ERR_SOCKET_CLOSED", `Socket is closed`); + } +} +export class ERR_SOCKET_DGRAM_IS_CONNECTED extends NodeError { + constructor() { + super("ERR_SOCKET_DGRAM_IS_CONNECTED", `Already connected`); + } +} +export class ERR_SOCKET_DGRAM_NOT_CONNECTED extends NodeError { + constructor() { + super("ERR_SOCKET_DGRAM_NOT_CONNECTED", `Not connected`); + } +} +export class ERR_SOCKET_DGRAM_NOT_RUNNING extends NodeError { + constructor() { + super("ERR_SOCKET_DGRAM_NOT_RUNNING", `Not running`); + } +} +export class ERR_SRI_PARSE extends NodeSyntaxError { + constructor(name: string, char: string, position: number) { + super( + "ERR_SRI_PARSE", + `Subresource Integrity string ${name} had an unexpected ${char} at position ${position}`, + ); + } +} +export class ERR_STREAM_ALREADY_FINISHED extends NodeError { + constructor(x: string) { + super( + "ERR_STREAM_ALREADY_FINISHED", + `Cannot call ${x} after a stream was finished`, + ); + } +} +export class ERR_STREAM_CANNOT_PIPE extends NodeError { + constructor() { + super("ERR_STREAM_CANNOT_PIPE", `Cannot pipe, not readable`); + } +} +export class ERR_STREAM_DESTROYED extends NodeError { + constructor(x: string) { + super( + "ERR_STREAM_DESTROYED", + `Cannot call ${x} after a stream was destroyed`, + ); + } +} +export class ERR_STREAM_NULL_VALUES extends NodeTypeError { + constructor() { + super("ERR_STREAM_NULL_VALUES", `May not write null values to stream`); + } +} +export class ERR_STREAM_PREMATURE_CLOSE extends NodeError { + constructor() { + super("ERR_STREAM_PREMATURE_CLOSE", `Premature close`); + } +} +export class ERR_STREAM_PUSH_AFTER_EOF extends NodeError { + constructor() { + super("ERR_STREAM_PUSH_AFTER_EOF", `stream.push() after EOF`); + } +} +export class ERR_STREAM_UNSHIFT_AFTER_END_EVENT extends NodeError { + constructor() { + super( + "ERR_STREAM_UNSHIFT_AFTER_END_EVENT", + `stream.unshift() after end event`, + ); + } +} +export class ERR_STREAM_WRAP extends NodeError { + constructor() { + super( + "ERR_STREAM_WRAP", + `Stream has StringDecoder set or is in objectMode`, + ); + } +} +export class ERR_STREAM_WRITE_AFTER_END extends NodeError { + constructor() { + super("ERR_STREAM_WRITE_AFTER_END", `write after end`); + } +} +export class ERR_SYNTHETIC extends NodeError { + constructor() { + super("ERR_SYNTHETIC", `JavaScript Callstack`); + } +} +export class ERR_TLS_CERT_ALTNAME_INVALID extends NodeError { + reason: string; + host: string; + cert: string; + + constructor(reason: string, host: string, cert: string) { + super( + "ERR_TLS_CERT_ALTNAME_INVALID", + `Hostname/IP does not match certificate's altnames: ${reason}`, + ); + this.reason = reason; + this.host = host; + this.cert = cert; + } +} +export class ERR_TLS_DH_PARAM_SIZE extends NodeError { + constructor(x: string) { + super("ERR_TLS_DH_PARAM_SIZE", `DH parameter size ${x} is less than 2048`); + } +} +export class ERR_TLS_HANDSHAKE_TIMEOUT extends NodeError { + constructor() { + super("ERR_TLS_HANDSHAKE_TIMEOUT", `TLS handshake timeout`); + } +} +export class ERR_TLS_INVALID_CONTEXT extends NodeTypeError { + constructor(x: string) { + super("ERR_TLS_INVALID_CONTEXT", `${x} must be a SecureContext`); + } +} +export class ERR_TLS_INVALID_STATE extends NodeError { + constructor() { + super( + "ERR_TLS_INVALID_STATE", + `TLS socket connection must be securely established`, + ); + } +} +export class ERR_TLS_INVALID_PROTOCOL_VERSION extends NodeTypeError { + constructor(protocol: string, x: string) { + super( + "ERR_TLS_INVALID_PROTOCOL_VERSION", + `${protocol} is not a valid ${x} TLS protocol version`, + ); + } +} +export class ERR_TLS_PROTOCOL_VERSION_CONFLICT extends NodeTypeError { + constructor(prevProtocol: string, protocol: string) { + super( + "ERR_TLS_PROTOCOL_VERSION_CONFLICT", + `TLS protocol version ${prevProtocol} conflicts with secureProtocol ${protocol}`, + ); + } +} +export class ERR_TLS_RENEGOTIATION_DISABLED extends NodeError { + constructor() { + super( + "ERR_TLS_RENEGOTIATION_DISABLED", + `TLS session renegotiation disabled for this socket`, + ); + } +} +export class ERR_TLS_REQUIRED_SERVER_NAME extends NodeError { + constructor() { + super( + "ERR_TLS_REQUIRED_SERVER_NAME", + `"servername" is required parameter for Server.addContext`, + ); + } +} +export class ERR_TLS_SESSION_ATTACK extends NodeError { + constructor() { + super( + "ERR_TLS_SESSION_ATTACK", + `TLS session renegotiation attack detected`, + ); + } +} +export class ERR_TLS_SNI_FROM_SERVER extends NodeError { + constructor() { + super( + "ERR_TLS_SNI_FROM_SERVER", + `Cannot issue SNI from a TLS server-side socket`, + ); + } +} +export class ERR_TRACE_EVENTS_CATEGORY_REQUIRED extends NodeTypeError { + constructor() { + super( + "ERR_TRACE_EVENTS_CATEGORY_REQUIRED", + `At least one category is required`, + ); + } +} +export class ERR_TRACE_EVENTS_UNAVAILABLE extends NodeError { + constructor() { + super("ERR_TRACE_EVENTS_UNAVAILABLE", `Trace events are unavailable`); + } +} +export class ERR_UNAVAILABLE_DURING_EXIT extends NodeError { + constructor() { + super( + "ERR_UNAVAILABLE_DURING_EXIT", + `Cannot call function in process exit handler`, + ); + } +} +export class ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET extends NodeError { + constructor() { + super( + "ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET", + "`process.setupUncaughtExceptionCapture()` was called while a capture callback was already active", + ); + } +} +export class ERR_UNESCAPED_CHARACTERS extends NodeTypeError { + constructor(x: string) { + super("ERR_UNESCAPED_CHARACTERS", `${x} contains unescaped characters`); + } +} +export class ERR_UNHANDLED_ERROR extends NodeError { + constructor(x: string) { + super("ERR_UNHANDLED_ERROR", `Unhandled error. (${x})`); + } +} +export class ERR_UNKNOWN_BUILTIN_MODULE extends NodeError { + constructor(x: string) { + super("ERR_UNKNOWN_BUILTIN_MODULE", `No such built-in module: ${x}`); + } +} +export class ERR_UNKNOWN_CREDENTIAL extends NodeError { + constructor(x: string, y: string) { + super("ERR_UNKNOWN_CREDENTIAL", `${x} identifier does not exist: ${y}`); + } +} +export class ERR_UNKNOWN_ENCODING extends NodeTypeError { + constructor(x: string) { + super("ERR_UNKNOWN_ENCODING", `Unknown encoding: ${x}`); + } +} +export class ERR_UNKNOWN_FILE_EXTENSION extends NodeTypeError { + constructor(x: string, y: string) { + super( + "ERR_UNKNOWN_FILE_EXTENSION", + `Unknown file extension "${x}" for ${y}`, + ); + } +} +export class ERR_UNKNOWN_MODULE_FORMAT extends NodeRangeError { + constructor(x: string) { + super("ERR_UNKNOWN_MODULE_FORMAT", `Unknown module format: ${x}`); + } +} +export class ERR_UNKNOWN_SIGNAL extends NodeTypeError { + constructor(x: string) { + super("ERR_UNKNOWN_SIGNAL", `Unknown signal: ${x}`); + } +} +export class ERR_UNSUPPORTED_DIR_IMPORT extends NodeError { + constructor(x: string, y: string) { + super( + "ERR_UNSUPPORTED_DIR_IMPORT", + `Directory import '${x}' is not supported resolving ES modules, imported from ${y}`, + ); + } +} +export class ERR_UNSUPPORTED_ESM_URL_SCHEME extends NodeError { + constructor() { + super( + "ERR_UNSUPPORTED_ESM_URL_SCHEME", + `Only file and data URLs are supported by the default ESM loader`, + ); + } +} +export class ERR_USE_AFTER_CLOSE extends NodeError { + constructor(x: string) { + super( + "ERR_USE_AFTER_CLOSE", + `${x} was closed`, + ); + } +} +export class ERR_V8BREAKITERATOR extends NodeError { + constructor() { + super( + "ERR_V8BREAKITERATOR", + `Full ICU data not installed. See https://github.com/nodejs/node/wiki/Intl`, + ); + } +} +export class ERR_VALID_PERFORMANCE_ENTRY_TYPE extends NodeError { + constructor() { + super( + "ERR_VALID_PERFORMANCE_ENTRY_TYPE", + `At least one valid performance entry type is required`, + ); + } +} +export class ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING extends NodeTypeError { + constructor() { + super( + "ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING", + `A dynamic import callback was not specified.`, + ); + } +} +export class ERR_VM_MODULE_ALREADY_LINKED extends NodeError { + constructor() { + super("ERR_VM_MODULE_ALREADY_LINKED", `Module has already been linked`); + } +} +export class ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA extends NodeError { + constructor() { + super( + "ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA", + `Cached data cannot be created for a module which has been evaluated`, + ); + } +} +export class ERR_VM_MODULE_DIFFERENT_CONTEXT extends NodeError { + constructor() { + super( + "ERR_VM_MODULE_DIFFERENT_CONTEXT", + `Linked modules must use the same context`, + ); + } +} +export class ERR_VM_MODULE_LINKING_ERRORED extends NodeError { + constructor() { + super( + "ERR_VM_MODULE_LINKING_ERRORED", + `Linking has already failed for the provided module`, + ); + } +} +export class ERR_VM_MODULE_NOT_MODULE extends NodeError { + constructor() { + super( + "ERR_VM_MODULE_NOT_MODULE", + `Provided module is not an instance of Module`, + ); + } +} +export class ERR_VM_MODULE_STATUS extends NodeError { + constructor(x: string) { + super("ERR_VM_MODULE_STATUS", `Module status ${x}`); + } +} +export class ERR_WASI_ALREADY_STARTED extends NodeError { + constructor() { + super("ERR_WASI_ALREADY_STARTED", `WASI instance has already started`); + } +} +export class ERR_WORKER_INIT_FAILED extends NodeError { + constructor(x: string) { + super("ERR_WORKER_INIT_FAILED", `Worker initialization failure: ${x}`); + } +} +export class ERR_WORKER_NOT_RUNNING extends NodeError { + constructor() { + super("ERR_WORKER_NOT_RUNNING", `Worker instance not running`); + } +} +export class ERR_WORKER_OUT_OF_MEMORY extends NodeError { + constructor(x: string) { + super( + "ERR_WORKER_OUT_OF_MEMORY", + `Worker terminated due to reaching memory limit: ${x}`, + ); + } +} +export class ERR_WORKER_UNSERIALIZABLE_ERROR extends NodeError { + constructor() { + super( + "ERR_WORKER_UNSERIALIZABLE_ERROR", + `Serializing an uncaught exception failed`, + ); + } +} +export class ERR_WORKER_UNSUPPORTED_EXTENSION extends NodeTypeError { + constructor(x: string) { + super( + "ERR_WORKER_UNSUPPORTED_EXTENSION", + `The worker script extension must be ".js", ".mjs", or ".cjs". Received "${x}"`, + ); + } +} +export class ERR_WORKER_UNSUPPORTED_OPERATION extends NodeTypeError { + constructor(x: string) { + super( + "ERR_WORKER_UNSUPPORTED_OPERATION", + `${x} is not supported in workers`, + ); + } +} +export class ERR_ZLIB_INITIALIZATION_FAILED extends NodeError { + constructor() { + super("ERR_ZLIB_INITIALIZATION_FAILED", `Initialization failed`); + } +} +export class ERR_FALSY_VALUE_REJECTION extends NodeError { + reason: string; + constructor(reason: string) { + super("ERR_FALSY_VALUE_REJECTION", "Promise was rejected with falsy value"); + this.reason = reason; + } +} +export class ERR_HTTP2_INVALID_SETTING_VALUE extends NodeRangeError { + actual: unknown; + min?: number; + max?: number; + + constructor(name: string, actual: unknown, min?: number, max?: number) { + super( + "ERR_HTTP2_INVALID_SETTING_VALUE", + `Invalid value for setting "${name}": ${actual}`, + ); + this.actual = actual; + if (min !== undefined) { + this.min = min; + this.max = max; + } + } +} +export class ERR_HTTP2_STREAM_CANCEL extends NodeError { + override cause?: Error; + constructor(error: Error) { + super( + "ERR_HTTP2_STREAM_CANCEL", + typeof error.message === "string" + ? `The pending stream has been canceled (caused by: ${error.message})` + : "The pending stream has been canceled", + ); + if (error) { + this.cause = error; + } + } +} + +export class ERR_INVALID_ADDRESS_FAMILY extends NodeRangeError { + host: string; + port: number; + constructor(addressType: string, host: string, port: number) { + super( + "ERR_INVALID_ADDRESS_FAMILY", + `Invalid address family: ${addressType} ${host}:${port}`, + ); + this.host = host; + this.port = port; + } +} + +export class ERR_INVALID_CHAR extends NodeTypeError { + constructor(name: string, field?: string) { + super( + "ERR_INVALID_CHAR", + field + ? `Invalid character in ${name}` + : `Invalid character in ${name} ["${field}"]`, + ); + } +} + +export class ERR_INVALID_OPT_VALUE extends NodeTypeError { + constructor(name: string, value: unknown) { + super( + "ERR_INVALID_OPT_VALUE", + `The value "${value}" is invalid for option "${name}"`, + ); + } +} + +export class ERR_INVALID_RETURN_PROPERTY extends NodeTypeError { + constructor(input: string, name: string, prop: string, value: string) { + super( + "ERR_INVALID_RETURN_PROPERTY", + `Expected a valid ${input} to be returned for the "${prop}" from the "${name}" function but got ${value}.`, + ); + } +} + +// deno-lint-ignore no-explicit-any +function buildReturnPropertyType(value: any) { + if (value && value.constructor && value.constructor.name) { + return `instance of ${value.constructor.name}`; + } else { + return `type ${typeof value}`; + } +} + +export class ERR_INVALID_RETURN_PROPERTY_VALUE extends NodeTypeError { + constructor(input: string, name: string, prop: string, value: unknown) { + super( + "ERR_INVALID_RETURN_PROPERTY_VALUE", + `Expected ${input} to be returned for the "${prop}" from the "${name}" function but got ${ + buildReturnPropertyType( + value, + ) + }.`, + ); + } +} + +export class ERR_INVALID_RETURN_VALUE extends NodeTypeError { + constructor(input: string, name: string, value: unknown) { + super( + "ERR_INVALID_RETURN_VALUE", + `Expected ${input} to be returned from the "${name}" function but got ${ + determineSpecificType( + value, + ) + }.`, + ); + } +} + +export class ERR_INVALID_URL extends NodeTypeError { + input: string; + constructor(input: string) { + super("ERR_INVALID_URL", `Invalid URL: ${input}`); + this.input = input; + } +} + +export class ERR_INVALID_URL_SCHEME extends NodeTypeError { + constructor(expected: string | [string] | [string, string]) { + expected = Array.isArray(expected) ? expected : [expected]; + const res = expected.length === 2 + ? `one of scheme ${expected[0]} or ${expected[1]}` + : `of scheme ${expected[0]}`; + super("ERR_INVALID_URL_SCHEME", `The URL must be ${res}`); + } +} + +export class ERR_MODULE_NOT_FOUND extends NodeError { + constructor(path: string, base: string, type: string = "package") { + super( + "ERR_MODULE_NOT_FOUND", + `Cannot find ${type} '${path}' imported from ${base}`, + ); + } +} + +export class ERR_INVALID_PACKAGE_CONFIG extends NodeError { + constructor(path: string, base?: string, message?: string) { + const msg = `Invalid package config ${path}${ + base ? ` while importing ${base}` : "" + }${message ? `. ${message}` : ""}`; + super("ERR_INVALID_PACKAGE_CONFIG", msg); + } +} + +export class ERR_INVALID_MODULE_SPECIFIER extends NodeTypeError { + constructor(request: string, reason: string, base?: string) { + super( + "ERR_INVALID_MODULE_SPECIFIER", + `Invalid module "${request}" ${reason}${ + base ? ` imported from ${base}` : "" + }`, + ); + } +} + +export class ERR_INVALID_PACKAGE_TARGET extends NodeError { + constructor( + pkgPath: string, + key: string, + // deno-lint-ignore no-explicit-any + target: any, + isImport?: boolean, + base?: string, + ) { + let msg: string; + const relError = typeof target === "string" && + !isImport && + target.length && + !target.startsWith("./"); + if (key === ".") { + assert(isImport === false); + msg = `Invalid "exports" main target ${JSON.stringify(target)} defined ` + + `in the package config ${pkgPath}package.json${ + base ? ` imported from ${base}` : "" + }${relError ? '; targets must start with "./"' : ""}`; + } else { + msg = `Invalid "${isImport ? "imports" : "exports"}" target ${ + JSON.stringify( + target, + ) + } defined for '${key}' in the package config ${pkgPath}package.json${ + base ? ` imported from ${base}` : "" + }${relError ? '; targets must start with "./"' : ""}`; + } + super("ERR_INVALID_PACKAGE_TARGET", msg); + } +} + +export class ERR_PACKAGE_IMPORT_NOT_DEFINED extends NodeTypeError { + constructor( + specifier: string, + packagePath: string | undefined, + base: string, + ) { + const msg = `Package import specifier "${specifier}" is not defined${ + packagePath ? ` in package ${packagePath}package.json` : "" + } imported from ${base}`; + + super("ERR_PACKAGE_IMPORT_NOT_DEFINED", msg); + } +} + +export class ERR_PACKAGE_PATH_NOT_EXPORTED extends NodeError { + constructor(subpath: string, pkgPath: string, basePath?: string) { + let msg: string; + if (subpath === ".") { + msg = `No "exports" main defined in ${pkgPath}package.json${ + basePath ? ` imported from ${basePath}` : "" + }`; + } else { + msg = + `Package subpath '${subpath}' is not defined by "exports" in ${pkgPath}package.json${ + basePath ? ` imported from ${basePath}` : "" + }`; + } + + super("ERR_PACKAGE_PATH_NOT_EXPORTED", msg); + } +} + +export class ERR_INTERNAL_ASSERTION extends NodeError { + constructor(message?: string) { + const suffix = "This is caused by either a bug in Node.js " + + "or incorrect usage of Node.js internals.\n" + + "Please open an issue with this stack trace at " + + "https://github.com/nodejs/node/issues\n"; + super( + "ERR_INTERNAL_ASSERTION", + message === undefined ? suffix : `${message}\n${suffix}`, + ); + } +} + +// Using `fs.rmdir` on a path that is a file results in an ENOENT error on Windows and an ENOTDIR error on POSIX. +export class ERR_FS_RMDIR_ENOTDIR extends NodeSystemError { + constructor(path: string) { + const code = isWindows ? "ENOENT" : "ENOTDIR"; + const ctx: NodeSystemErrorCtx = { + message: "not a directory", + path, + syscall: "rmdir", + code, + errno: isWindows ? osConstants.errno.ENOENT : osConstants.errno.ENOTDIR, + }; + super(code, ctx, "Path is not a directory"); + } +} + +interface UvExceptionContext { + syscall: string; + path?: string; +} +export function denoErrorToNodeError(e: Error, ctx: UvExceptionContext) { + const errno = extractOsErrorNumberFromErrorMessage(e); + if (typeof errno === "undefined") { + return e; + } + + const ex = uvException({ + errno: mapSysErrnoToUvErrno(errno), + ...ctx, + }); + return ex; +} + +function extractOsErrorNumberFromErrorMessage(e: unknown): number | undefined { + const match = e instanceof Error + ? e.message.match(/\(os error (\d+)\)/) + : false; + + if (match) { + return +match[1]; + } + + return undefined; +} + +export function connResetException(msg: string) { + const ex = new Error(msg); + // deno-lint-ignore no-explicit-any + (ex as any).code = "ECONNRESET"; + return ex; +} + +export function aggregateTwoErrors( + innerError: AggregateError, + outerError: AggregateError & { code: string }, +) { + if (innerError && outerError && innerError !== outerError) { + if (Array.isArray(outerError.errors)) { + // If `outerError` is already an `AggregateError`. + outerError.errors.push(innerError); + return outerError; + } + // eslint-disable-next-line no-restricted-syntax + const err = new AggregateError( + [ + outerError, + innerError, + ], + outerError.message, + ); + // deno-lint-ignore no-explicit-any + (err as any).code = outerError.code; + return err; + } + return innerError || outerError; +} +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_BUFFER_OUT_OF_BOUNDS = ERR_BUFFER_OUT_OF_BOUNDS; +codes.ERR_UNKNOWN_ENCODING = ERR_UNKNOWN_ENCODING; +// TODO(kt3k): assign all error classes here. + +/** + * This creates a generic Node.js error. + * + * @param message The error message. + * @param errorProperties Object with additional properties to be added to the error. + * @returns + */ +const genericNodeError = hideStackFrames( + function genericNodeError(message, errorProperties) { + // eslint-disable-next-line no-restricted-syntax + const err = new Error(message); + Object.assign(err, errorProperties); + + return err; + }, +); + +/** + * Determine the specific type of a value for type-mismatch errors. + * @param {*} value + * @returns {string} + */ +// deno-lint-ignore no-explicit-any +function determineSpecificType(value: any) { + if (value == null) { + return "" + value; + } + if (typeof value === "function" && value.name) { + return `function ${value.name}`; + } + if (typeof value === "object") { + if (value.constructor?.name) { + return `an instance of ${value.constructor.name}`; + } + return `${inspect(value, { depth: -1 })}`; + } + let inspected = inspect(value, { colors: false }); + if (inspected.length > 28) inspected = `${inspected.slice(0, 25)}...`; + + return `type ${typeof value} (${inspected})`; +} + +export { codes, genericNodeError, hideStackFrames }; + +export default { + AbortError, + ERR_AMBIGUOUS_ARGUMENT, + ERR_ARG_NOT_ITERABLE, + ERR_ASSERTION, + ERR_ASYNC_CALLBACK, + ERR_ASYNC_TYPE, + ERR_BROTLI_INVALID_PARAM, + ERR_BUFFER_OUT_OF_BOUNDS, + ERR_BUFFER_TOO_LARGE, + ERR_CANNOT_WATCH_SIGINT, + ERR_CHILD_CLOSED_BEFORE_REPLY, + ERR_CHILD_PROCESS_IPC_REQUIRED, + ERR_CHILD_PROCESS_STDIO_MAXBUFFER, + ERR_CONSOLE_WRITABLE_STREAM, + ERR_CONTEXT_NOT_INITIALIZED, + ERR_CPU_USAGE, + ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED, + ERR_CRYPTO_ECDH_INVALID_FORMAT, + ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY, + ERR_CRYPTO_ENGINE_UNKNOWN, + ERR_CRYPTO_FIPS_FORCED, + ERR_CRYPTO_FIPS_UNAVAILABLE, + ERR_CRYPTO_HASH_FINALIZED, + ERR_CRYPTO_HASH_UPDATE_FAILED, + ERR_CRYPTO_INCOMPATIBLE_KEY, + ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS, + ERR_CRYPTO_INVALID_DIGEST, + ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE, + ERR_CRYPTO_INVALID_STATE, + ERR_CRYPTO_PBKDF2_ERROR, + ERR_CRYPTO_SCRYPT_INVALID_PARAMETER, + ERR_CRYPTO_SCRYPT_NOT_SUPPORTED, + ERR_CRYPTO_SIGN_KEY_REQUIRED, + ERR_DIR_CLOSED, + ERR_DIR_CONCURRENT_OPERATION, + ERR_DNS_SET_SERVERS_FAILED, + ERR_DOMAIN_CALLBACK_NOT_AVAILABLE, + ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE, + ERR_ENCODING_INVALID_ENCODED_DATA, + ERR_ENCODING_NOT_SUPPORTED, + ERR_EVAL_ESM_CANNOT_PRINT, + ERR_EVENT_RECURSION, + ERR_FALSY_VALUE_REJECTION, + ERR_FEATURE_UNAVAILABLE_ON_PLATFORM, + ERR_FS_EISDIR, + ERR_FS_FILE_TOO_LARGE, + ERR_FS_INVALID_SYMLINK_TYPE, + ERR_FS_RMDIR_ENOTDIR, + ERR_HTTP2_ALTSVC_INVALID_ORIGIN, + ERR_HTTP2_ALTSVC_LENGTH, + ERR_HTTP2_CONNECT_AUTHORITY, + ERR_HTTP2_CONNECT_PATH, + ERR_HTTP2_CONNECT_SCHEME, + ERR_HTTP2_GOAWAY_SESSION, + ERR_HTTP2_HEADERS_AFTER_RESPOND, + ERR_HTTP2_HEADERS_SENT, + ERR_HTTP2_HEADER_SINGLE_VALUE, + ERR_HTTP2_INFO_STATUS_NOT_ALLOWED, + ERR_HTTP2_INVALID_CONNECTION_HEADERS, + ERR_HTTP2_INVALID_HEADER_VALUE, + ERR_HTTP2_INVALID_INFO_STATUS, + ERR_HTTP2_INVALID_ORIGIN, + ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH, + ERR_HTTP2_INVALID_PSEUDOHEADER, + ERR_HTTP2_INVALID_SESSION, + ERR_HTTP2_INVALID_SETTING_VALUE, + ERR_HTTP2_INVALID_STREAM, + ERR_HTTP2_MAX_PENDING_SETTINGS_ACK, + ERR_HTTP2_NESTED_PUSH, + ERR_HTTP2_NO_SOCKET_MANIPULATION, + ERR_HTTP2_ORIGIN_LENGTH, + ERR_HTTP2_OUT_OF_STREAMS, + ERR_HTTP2_PAYLOAD_FORBIDDEN, + ERR_HTTP2_PING_CANCEL, + ERR_HTTP2_PING_LENGTH, + ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED, + ERR_HTTP2_PUSH_DISABLED, + ERR_HTTP2_SEND_FILE, + ERR_HTTP2_SEND_FILE_NOSEEK, + ERR_HTTP2_SESSION_ERROR, + ERR_HTTP2_SETTINGS_CANCEL, + ERR_HTTP2_SOCKET_BOUND, + ERR_HTTP2_SOCKET_UNBOUND, + ERR_HTTP2_STATUS_101, + ERR_HTTP2_STATUS_INVALID, + ERR_HTTP2_STREAM_CANCEL, + ERR_HTTP2_STREAM_ERROR, + ERR_HTTP2_STREAM_SELF_DEPENDENCY, + ERR_HTTP2_TRAILERS_ALREADY_SENT, + ERR_HTTP2_TRAILERS_NOT_READY, + ERR_HTTP2_UNSUPPORTED_PROTOCOL, + ERR_HTTP_HEADERS_SENT, + ERR_HTTP_INVALID_HEADER_VALUE, + ERR_HTTP_INVALID_STATUS_CODE, + ERR_HTTP_SOCKET_ENCODING, + ERR_HTTP_TRAILER_INVALID, + ERR_INCOMPATIBLE_OPTION_PAIR, + ERR_INPUT_TYPE_NOT_ALLOWED, + ERR_INSPECTOR_ALREADY_ACTIVATED, + ERR_INSPECTOR_ALREADY_CONNECTED, + ERR_INSPECTOR_CLOSED, + ERR_INSPECTOR_COMMAND, + ERR_INSPECTOR_NOT_ACTIVE, + ERR_INSPECTOR_NOT_AVAILABLE, + ERR_INSPECTOR_NOT_CONNECTED, + ERR_INSPECTOR_NOT_WORKER, + ERR_INTERNAL_ASSERTION, + ERR_INVALID_ADDRESS_FAMILY, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_TYPE_RANGE, + ERR_INVALID_ARG_VALUE, + ERR_INVALID_ARG_VALUE_RANGE, + ERR_INVALID_ASYNC_ID, + ERR_INVALID_BUFFER_SIZE, + ERR_INVALID_CHAR, + ERR_INVALID_CURSOR_POS, + ERR_INVALID_FD, + ERR_INVALID_FD_TYPE, + ERR_INVALID_FILE_URL_HOST, + ERR_INVALID_FILE_URL_PATH, + ERR_INVALID_HANDLE_TYPE, + ERR_INVALID_HTTP_TOKEN, + ERR_INVALID_IP_ADDRESS, + ERR_INVALID_MODULE_SPECIFIER, + ERR_INVALID_OPT_VALUE, + ERR_INVALID_OPT_VALUE_ENCODING, + ERR_INVALID_PACKAGE_CONFIG, + ERR_INVALID_PACKAGE_TARGET, + ERR_INVALID_PERFORMANCE_MARK, + ERR_INVALID_PROTOCOL, + ERR_INVALID_REPL_EVAL_CONFIG, + ERR_INVALID_REPL_INPUT, + ERR_INVALID_RETURN_PROPERTY, + ERR_INVALID_RETURN_PROPERTY_VALUE, + ERR_INVALID_RETURN_VALUE, + ERR_INVALID_SYNC_FORK_INPUT, + ERR_INVALID_THIS, + ERR_INVALID_TUPLE, + ERR_INVALID_URI, + ERR_INVALID_URL, + ERR_INVALID_URL_SCHEME, + ERR_IPC_CHANNEL_CLOSED, + ERR_IPC_DISCONNECTED, + ERR_IPC_ONE_PIPE, + ERR_IPC_SYNC_FORK, + ERR_MANIFEST_DEPENDENCY_MISSING, + ERR_MANIFEST_INTEGRITY_MISMATCH, + ERR_MANIFEST_INVALID_RESOURCE_FIELD, + ERR_MANIFEST_TDZ, + ERR_MANIFEST_UNKNOWN_ONERROR, + ERR_METHOD_NOT_IMPLEMENTED, + ERR_MISSING_ARGS, + ERR_MISSING_OPTION, + ERR_MODULE_NOT_FOUND, + ERR_MULTIPLE_CALLBACK, + ERR_NAPI_CONS_FUNCTION, + ERR_NAPI_INVALID_DATAVIEW_ARGS, + ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT, + ERR_NAPI_INVALID_TYPEDARRAY_LENGTH, + ERR_NO_CRYPTO, + ERR_NO_ICU, + ERR_OUT_OF_RANGE, + ERR_PACKAGE_IMPORT_NOT_DEFINED, + ERR_PACKAGE_PATH_NOT_EXPORTED, + ERR_QUICCLIENTSESSION_FAILED, + ERR_QUICCLIENTSESSION_FAILED_SETSOCKET, + ERR_QUICSESSION_DESTROYED, + ERR_QUICSESSION_INVALID_DCID, + ERR_QUICSESSION_UPDATEKEY, + ERR_QUICSOCKET_DESTROYED, + ERR_QUICSOCKET_INVALID_STATELESS_RESET_SECRET_LENGTH, + ERR_QUICSOCKET_LISTENING, + ERR_QUICSOCKET_UNBOUND, + ERR_QUICSTREAM_DESTROYED, + ERR_QUICSTREAM_INVALID_PUSH, + ERR_QUICSTREAM_OPEN_FAILED, + ERR_QUICSTREAM_UNSUPPORTED_PUSH, + ERR_QUIC_TLS13_REQUIRED, + ERR_SCRIPT_EXECUTION_INTERRUPTED, + ERR_SERVER_ALREADY_LISTEN, + ERR_SERVER_NOT_RUNNING, + ERR_SOCKET_ALREADY_BOUND, + ERR_SOCKET_BAD_BUFFER_SIZE, + ERR_SOCKET_BAD_PORT, + ERR_SOCKET_BAD_TYPE, + ERR_SOCKET_BUFFER_SIZE, + ERR_SOCKET_CLOSED, + ERR_SOCKET_DGRAM_IS_CONNECTED, + ERR_SOCKET_DGRAM_NOT_CONNECTED, + ERR_SOCKET_DGRAM_NOT_RUNNING, + ERR_SRI_PARSE, + ERR_STREAM_ALREADY_FINISHED, + ERR_STREAM_CANNOT_PIPE, + ERR_STREAM_DESTROYED, + ERR_STREAM_NULL_VALUES, + ERR_STREAM_PREMATURE_CLOSE, + ERR_STREAM_PUSH_AFTER_EOF, + ERR_STREAM_UNSHIFT_AFTER_END_EVENT, + ERR_STREAM_WRAP, + ERR_STREAM_WRITE_AFTER_END, + ERR_SYNTHETIC, + ERR_TLS_CERT_ALTNAME_INVALID, + ERR_TLS_DH_PARAM_SIZE, + ERR_TLS_HANDSHAKE_TIMEOUT, + ERR_TLS_INVALID_CONTEXT, + ERR_TLS_INVALID_PROTOCOL_VERSION, + ERR_TLS_INVALID_STATE, + ERR_TLS_PROTOCOL_VERSION_CONFLICT, + ERR_TLS_RENEGOTIATION_DISABLED, + ERR_TLS_REQUIRED_SERVER_NAME, + ERR_TLS_SESSION_ATTACK, + ERR_TLS_SNI_FROM_SERVER, + ERR_TRACE_EVENTS_CATEGORY_REQUIRED, + ERR_TRACE_EVENTS_UNAVAILABLE, + ERR_UNAVAILABLE_DURING_EXIT, + ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET, + ERR_UNESCAPED_CHARACTERS, + ERR_UNHANDLED_ERROR, + ERR_UNKNOWN_BUILTIN_MODULE, + ERR_UNKNOWN_CREDENTIAL, + ERR_UNKNOWN_ENCODING, + ERR_UNKNOWN_FILE_EXTENSION, + ERR_UNKNOWN_MODULE_FORMAT, + ERR_UNKNOWN_SIGNAL, + ERR_UNSUPPORTED_DIR_IMPORT, + ERR_UNSUPPORTED_ESM_URL_SCHEME, + ERR_USE_AFTER_CLOSE, + ERR_V8BREAKITERATOR, + ERR_VALID_PERFORMANCE_ENTRY_TYPE, + ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING, + ERR_VM_MODULE_ALREADY_LINKED, + ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA, + ERR_VM_MODULE_DIFFERENT_CONTEXT, + ERR_VM_MODULE_LINKING_ERRORED, + ERR_VM_MODULE_NOT_MODULE, + ERR_VM_MODULE_STATUS, + ERR_WASI_ALREADY_STARTED, + ERR_WORKER_INIT_FAILED, + ERR_WORKER_NOT_RUNNING, + ERR_WORKER_OUT_OF_MEMORY, + ERR_WORKER_UNSERIALIZABLE_ERROR, + ERR_WORKER_UNSUPPORTED_EXTENSION, + ERR_WORKER_UNSUPPORTED_OPERATION, + ERR_ZLIB_INITIALIZATION_FAILED, + NodeError, + NodeErrorAbstraction, + NodeRangeError, + NodeSyntaxError, + NodeTypeError, + NodeURIError, + aggregateTwoErrors, + codes, + connResetException, + denoErrorToNodeError, + dnsException, + errnoException, + errorMap, + exceptionWithHostPort, + genericNodeError, + hideStackFrames, + isStackOverflowError, + uvException, + uvExceptionWithHostPort, +}; diff --git a/ext/node/polyfills/internal/event_target.mjs b/ext/node/polyfills/internal/event_target.mjs new file mode 100644 index 000000000..d542fba94 --- /dev/null +++ b/ext/node/polyfills/internal/event_target.mjs @@ -0,0 +1,1111 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Node.js contributors. All rights reserved. MIT License. + +import { + ERR_EVENT_RECURSION, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_THIS, + ERR_MISSING_ARGS, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { validateObject, validateString } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { emitWarning } from "internal:deno_node/polyfills/process.ts"; +import { nextTick } from "internal:deno_node/polyfills/_next_tick.ts"; +import { Event as WebEvent, EventTarget as WebEventTarget } from "internal:deno_web/02_event.js"; + +import { + customInspectSymbol, + kEmptyObject, + kEnumerableProperty, +} from "internal:deno_node/polyfills/internal/util.mjs"; +import { inspect } from "internal:deno_node/polyfills/util.ts"; + +const kIsEventTarget = Symbol.for("nodejs.event_target"); +const kIsNodeEventTarget = Symbol("kIsNodeEventTarget"); + +import { EventEmitter } from "internal:deno_node/polyfills/events.ts"; +const { + kMaxEventTargetListeners, + kMaxEventTargetListenersWarned, +} = EventEmitter; + +const kEvents = Symbol("kEvents"); +const kIsBeingDispatched = Symbol("kIsBeingDispatched"); +const kStop = Symbol("kStop"); +const kTarget = Symbol("kTarget"); +const kHandlers = Symbol("khandlers"); +const kWeakHandler = Symbol("kWeak"); + +const kHybridDispatch = Symbol.for("nodejs.internal.kHybridDispatch"); +const kCreateEvent = Symbol("kCreateEvent"); +const kNewListener = Symbol("kNewListener"); +const kRemoveListener = Symbol("kRemoveListener"); +const kIsNodeStyleListener = Symbol("kIsNodeStyleListener"); +const kTrustEvent = Symbol("kTrustEvent"); + +const kType = Symbol("type"); +const kDetail = Symbol("detail"); +const kDefaultPrevented = Symbol("defaultPrevented"); +const kCancelable = Symbol("cancelable"); +const kTimestamp = Symbol("timestamp"); +const kBubbles = Symbol("bubbles"); +const kComposed = Symbol("composed"); +const kPropagationStopped = Symbol("propagationStopped"); + +function isEvent(value) { + return typeof value?.[kType] === "string"; +} + +class Event extends WebEvent { + /** + * @param {string} type + * @param {{ + * bubbles?: boolean, + * cancelable?: boolean, + * composed?: boolean, + * }} [options] + */ + constructor(type, options = null) { + super(type, options); + if (arguments.length === 0) { + throw new ERR_MISSING_ARGS("type"); + } + validateObject(options, "options", { + allowArray: true, + allowFunction: true, + nullable: true, + }); + const { cancelable, bubbles, composed } = { ...options }; + this[kCancelable] = !!cancelable; + this[kBubbles] = !!bubbles; + this[kComposed] = !!composed; + this[kType] = `${type}`; + this[kDefaultPrevented] = false; + this[kTimestamp] = performance.now(); + this[kPropagationStopped] = false; + this[kTarget] = null; + this[kIsBeingDispatched] = false; + } + + [customInspectSymbol](depth, options) { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + const name = this.constructor.name; + if (depth < 0) { + return name; + } + + const opts = Object.assign({}, options, { + depth: NumberIsInteger(options.depth) ? options.depth - 1 : options.depth, + }); + + return `${name} ${ + inspect({ + type: this[kType], + defaultPrevented: this[kDefaultPrevented], + cancelable: this[kCancelable], + timeStamp: this[kTimestamp], + }, opts) + }`; + } + + stopImmediatePropagation() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + this[kStop] = true; + } + + preventDefault() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + this[kDefaultPrevented] = true; + } + + /** + * @type {EventTarget} + */ + get target() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kTarget]; + } + + /** + * @type {EventTarget} + */ + get currentTarget() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kTarget]; + } + + /** + * @type {EventTarget} + */ + get srcElement() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kTarget]; + } + + /** + * @type {string} + */ + get type() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kType]; + } + + /** + * @type {boolean} + */ + get cancelable() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kCancelable]; + } + + /** + * @type {boolean} + */ + get defaultPrevented() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kCancelable] && this[kDefaultPrevented]; + } + + /** + * @type {number} + */ + get timeStamp() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kTimestamp]; + } + + // The following are non-op and unused properties/methods from Web API Event. + // These are not supported in Node.js and are provided purely for + // API completeness. + /** + * @returns {EventTarget[]} + */ + composedPath() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kIsBeingDispatched] ? [this[kTarget]] : []; + } + + /** + * @type {boolean} + */ + get returnValue() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return !this.defaultPrevented; + } + + /** + * @type {boolean} + */ + get bubbles() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kBubbles]; + } + + /** + * @type {boolean} + */ + get composed() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kComposed]; + } + + /** + * @type {number} + */ + get eventPhase() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kIsBeingDispatched] ? Event.AT_TARGET : Event.NONE; + } + + /** + * @type {boolean} + */ + get cancelBubble() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kPropagationStopped]; + } + + /** + * @type {boolean} + */ + set cancelBubble(value) { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + if (value) { + this.stopPropagation(); + } + } + + stopPropagation() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + this[kPropagationStopped] = true; + } + + static NONE = 0; + static CAPTURING_PHASE = 1; + static AT_TARGET = 2; + static BUBBLING_PHASE = 3; +} + +Object.defineProperties( + Event.prototype, + { + [Symbol.toStringTag]: { + writable: true, + enumerable: false, + configurable: true, + value: "Event", + }, + stopImmediatePropagation: kEnumerableProperty, + preventDefault: kEnumerableProperty, + target: kEnumerableProperty, + currentTarget: kEnumerableProperty, + srcElement: kEnumerableProperty, + type: kEnumerableProperty, + cancelable: kEnumerableProperty, + defaultPrevented: kEnumerableProperty, + timeStamp: kEnumerableProperty, + composedPath: kEnumerableProperty, + returnValue: kEnumerableProperty, + bubbles: kEnumerableProperty, + composed: kEnumerableProperty, + eventPhase: kEnumerableProperty, + cancelBubble: kEnumerableProperty, + stopPropagation: kEnumerableProperty, + }, +); + +function isCustomEvent(value) { + return isEvent(value) && (value?.[kDetail] !== undefined); +} + +class CustomEvent extends Event { + /** + * @constructor + * @param {string} type + * @param {{ + * bubbles?: boolean, + * cancelable?: boolean, + * composed?: boolean, + * detail?: any, + * }} [options] + */ + constructor(type, options = kEmptyObject) { + if (arguments.length === 0) { + throw new ERR_MISSING_ARGS("type"); + } + super(type, options); + this[kDetail] = options?.detail ?? null; + } + + /** + * @type {any} + */ + get detail() { + if (!isCustomEvent(this)) { + throw new ERR_INVALID_THIS("CustomEvent"); + } + return this[kDetail]; + } +} + +Object.defineProperties(CustomEvent.prototype, { + [Symbol.toStringTag]: { + __proto__: null, + writable: false, + enumerable: false, + configurable: true, + value: "CustomEvent", + }, + detail: kEnumerableProperty, +}); + +class NodeCustomEvent extends Event { + constructor(type, options) { + super(type, options); + if (options?.detail) { + this.detail = options.detail; + } + } +} + +// Weak listener cleanup +// This has to be lazy for snapshots to work +let weakListenersState = null; +// The resource needs to retain the callback so that it doesn't +// get garbage collected now that it's weak. +let objectToWeakListenerMap = null; +function weakListeners() { + weakListenersState ??= new FinalizationRegistry( + (listener) => listener.remove(), + ); + objectToWeakListenerMap ??= new WeakMap(); + return { registry: weakListenersState, map: objectToWeakListenerMap }; +} + +// The listeners for an EventTarget are maintained as a linked list. +// Unfortunately, the way EventTarget is defined, listeners are accounted +// using the tuple [handler,capture], and even if we don't actually make +// use of capture or bubbling, in order to be spec compliant we have to +// take on the additional complexity of supporting it. Fortunately, using +// the linked list makes dispatching faster, even if adding/removing is +// slower. +class Listener { + constructor( + previous, + listener, + once, + capture, + passive, + isNodeStyleListener, + weak, + ) { + this.next = undefined; + if (previous !== undefined) { + previous.next = this; + } + this.previous = previous; + this.listener = listener; + // TODO(benjamingr) these 4 can be 'flags' to save 3 slots + this.once = once; + this.capture = capture; + this.passive = passive; + this.isNodeStyleListener = isNodeStyleListener; + this.removed = false; + this.weak = Boolean(weak); // Don't retain the object + + if (this.weak) { + this.callback = new WeakRef(listener); + weakListeners().registry.register(listener, this, this); + // Make the retainer retain the listener in a WeakMap + weakListeners().map.set(weak, listener); + this.listener = this.callback; + } else if (typeof listener === "function") { + this.callback = listener; + this.listener = listener; + } else { + this.callback = Function.prototype.bind.call( + listener.handleEvent, + listener, + ); + this.listener = listener; + } + } + + same(listener, capture) { + const myListener = this.weak ? this.listener.deref() : this.listener; + return myListener === listener && this.capture === capture; + } + + remove() { + if (this.previous !== undefined) { + this.previous.next = this.next; + } + if (this.next !== undefined) { + this.next.previous = this.previous; + } + this.removed = true; + if (this.weak) { + weakListeners().registry.unregister(this); + } + } +} + +function initEventTarget(self) { + self[kEvents] = new Map(); + self[kMaxEventTargetListeners] = EventEmitter.defaultMaxListeners; + self[kMaxEventTargetListenersWarned] = false; +} + +class EventTarget extends WebEventTarget { + // Used in checking whether an object is an EventTarget. This is a well-known + // symbol as EventTarget may be used cross-realm. + // Ref: https://github.com/nodejs/node/pull/33661 + static [kIsEventTarget] = true; + + constructor() { + super(); + initEventTarget(this); + } + + [kNewListener](size, type, _listener, _once, _capture, _passive, _weak) { + if ( + this[kMaxEventTargetListeners] > 0 && + size > this[kMaxEventTargetListeners] && + !this[kMaxEventTargetListenersWarned] + ) { + this[kMaxEventTargetListenersWarned] = true; + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + const w = new Error( + "Possible EventTarget memory leak detected. " + + `${size} ${type} listeners ` + + `added to ${inspect(this, { depth: -1 })}. Use ` + + "events.setMaxListeners() to increase limit", + ); + w.name = "MaxListenersExceededWarning"; + w.target = this; + w.type = type; + w.count = size; + emitWarning(w); + } + } + [kRemoveListener](_size, _type, _listener, _capture) {} + + /** + * @callback EventTargetCallback + * @param {Event} event + */ + + /** + * @typedef {{ handleEvent: EventTargetCallback }} EventListener + */ + + /** + * @param {string} type + * @param {EventTargetCallback|EventListener} listener + * @param {{ + * capture?: boolean, + * once?: boolean, + * passive?: boolean, + * signal?: AbortSignal + * }} [options] + */ + addEventListener(type, listener, options = {}) { + if (!isEventTarget(this)) { + throw new ERR_INVALID_THIS("EventTarget"); + } + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("type", "listener"); + } + + // We validateOptions before the shouldAddListeners check because the spec + // requires us to hit getters. + const { + once, + capture, + passive, + signal, + isNodeStyleListener, + weak, + } = validateEventListenerOptions(options); + + if (!shouldAddListener(listener)) { + // The DOM silently allows passing undefined as a second argument + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + const w = new Error( + `addEventListener called with ${listener}` + + " which has no effect.", + ); + w.name = "AddEventListenerArgumentTypeWarning"; + w.target = this; + w.type = type; + emitWarning(w); + return; + } + type = String(type); + + if (signal) { + if (signal.aborted) { + return; + } + // TODO(benjamingr) make this weak somehow? ideally the signal would + // not prevent the event target from GC. + signal.addEventListener("abort", () => { + this.removeEventListener(type, listener, options); + }, { once: true, [kWeakHandler]: this }); + } + + let root = this[kEvents].get(type); + + if (root === undefined) { + root = { size: 1, next: undefined }; + // This is the first handler in our linked list. + new Listener( + root, + listener, + once, + capture, + passive, + isNodeStyleListener, + weak, + ); + this[kNewListener]( + root.size, + type, + listener, + once, + capture, + passive, + weak, + ); + this[kEvents].set(type, root); + return; + } + + let handler = root.next; + let previous = root; + + // We have to walk the linked list to see if we have a match + while (handler !== undefined && !handler.same(listener, capture)) { + previous = handler; + handler = handler.next; + } + + if (handler !== undefined) { // Duplicate! Ignore + return; + } + + new Listener( + previous, + listener, + once, + capture, + passive, + isNodeStyleListener, + weak, + ); + root.size++; + this[kNewListener](root.size, type, listener, once, capture, passive, weak); + } + + /** + * @param {string} type + * @param {EventTargetCallback|EventListener} listener + * @param {{ + * capture?: boolean, + * }} [options] + */ + removeEventListener(type, listener, options = {}) { + if (!isEventTarget(this)) { + throw new ERR_INVALID_THIS("EventTarget"); + } + if (!shouldAddListener(listener)) { + return; + } + + type = String(type); + const capture = options?.capture === true; + + const root = this[kEvents].get(type); + if (root === undefined || root.next === undefined) { + return; + } + + let handler = root.next; + while (handler !== undefined) { + if (handler.same(listener, capture)) { + handler.remove(); + root.size--; + if (root.size === 0) { + this[kEvents].delete(type); + } + this[kRemoveListener](root.size, type, listener, capture); + break; + } + handler = handler.next; + } + } + + /** + * @param {Event} event + */ + dispatchEvent(event) { + if (!isEventTarget(this)) { + throw new ERR_INVALID_THIS("EventTarget"); + } + + if (!(event instanceof globalThis.Event)) { + throw new ERR_INVALID_ARG_TYPE("event", "Event", event); + } + + if (event[kIsBeingDispatched]) { + throw new ERR_EVENT_RECURSION(event.type); + } + + this[kHybridDispatch](event, event.type, event); + + return event.defaultPrevented !== true; + } + + [kHybridDispatch](nodeValue, type, event) { + const createEvent = () => { + if (event === undefined) { + event = this[kCreateEvent](nodeValue, type); + event[kTarget] = this; + event[kIsBeingDispatched] = true; + } + return event; + }; + if (event !== undefined) { + event[kTarget] = this; + event[kIsBeingDispatched] = true; + } + + const root = this[kEvents].get(type); + if (root === undefined || root.next === undefined) { + if (event !== undefined) { + event[kIsBeingDispatched] = false; + } + return true; + } + + let handler = root.next; + let next; + + while ( + handler !== undefined && + (handler.passive || event?.[kStop] !== true) + ) { + // Cache the next item in case this iteration removes the current one + next = handler.next; + + if (handler.removed) { + // Deal with the case an event is removed while event handlers are + // Being processed (removeEventListener called from a listener) + handler = next; + continue; + } + if (handler.once) { + handler.remove(); + root.size--; + const { listener, capture } = handler; + this[kRemoveListener](root.size, type, listener, capture); + } + + try { + let arg; + if (handler.isNodeStyleListener) { + arg = nodeValue; + } else { + arg = createEvent(); + } + const callback = handler.weak + ? handler.callback.deref() + : handler.callback; + let result; + if (callback) { + result = callback.call(this, arg); + if (!handler.isNodeStyleListener) { + arg[kIsBeingDispatched] = false; + } + } + if (result !== undefined && result !== null) { + addCatch(result); + } + } catch (err) { + emitUncaughtException(err); + } + + handler = next; + } + + if (event !== undefined) { + event[kIsBeingDispatched] = false; + } + } + + [kCreateEvent](nodeValue, type) { + return new NodeCustomEvent(type, { detail: nodeValue }); + } + [customInspectSymbol](depth, options) { + if (!isEventTarget(this)) { + throw new ERR_INVALID_THIS("EventTarget"); + } + const name = this.constructor.name; + if (depth < 0) { + return name; + } + + const opts = ObjectAssign({}, options, { + depth: Number.isInteger(options.depth) + ? options.depth - 1 + : options.depth, + }); + + return `${name} ${inspect({}, opts)}`; + } +} + +Object.defineProperties(EventTarget.prototype, { + addEventListener: kEnumerableProperty, + removeEventListener: kEnumerableProperty, + dispatchEvent: kEnumerableProperty, + [Symbol.toStringTag]: { + writable: true, + enumerable: false, + configurable: true, + value: "EventTarget", + }, +}); + +function initNodeEventTarget(self) { + initEventTarget(self); +} + +class NodeEventTarget extends EventTarget { + static [kIsNodeEventTarget] = true; + static defaultMaxListeners = 10; + + constructor() { + super(); + initNodeEventTarget(this); + } + + /** + * @param {number} n + */ + setMaxListeners(n) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + EventEmitter.setMaxListeners(n, this); + } + + /** + * @returns {number} + */ + getMaxListeners() { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + return this[kMaxEventTargetListeners]; + } + + /** + * @returns {string[]} + */ + eventNames() { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + return Array.from(this[kEvents].keys()); + } + + /** + * @param {string} [type] + * @returns {number} + */ + listenerCount(type) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + const root = this[kEvents].get(String(type)); + return root !== undefined ? root.size : 0; + } + + /** + * @param {string} type + * @param {EventTargetCallback|EventListener} listener + * @param {{ + * capture?: boolean, + * }} [options] + * @returns {NodeEventTarget} + */ + off(type, listener, options) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + this.removeEventListener(type, listener, options); + return this; + } + + /** + * @param {string} type + * @param {EventTargetCallback|EventListener} listener + * @param {{ + * capture?: boolean, + * }} [options] + * @returns {NodeEventTarget} + */ + removeListener(type, listener, options) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + this.removeEventListener(type, listener, options); + return this; + } + + /** + * @param {string} type + * @param {EventTargetCallback|EventListener} listener + * @returns {NodeEventTarget} + */ + on(type, listener) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + this.addEventListener(type, listener, { [kIsNodeStyleListener]: true }); + return this; + } + + /** + * @param {string} type + * @param {EventTargetCallback|EventListener} listener + * @returns {NodeEventTarget} + */ + addListener(type, listener) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + this.addEventListener(type, listener, { [kIsNodeStyleListener]: true }); + return this; + } + + /** + * @param {string} type + * @param {any} arg + * @returns {boolean} + */ + emit(type, arg) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + validateString(type, "type"); + const hadListeners = this.listenerCount(type) > 0; + this[kHybridDispatch](arg, type); + return hadListeners; + } + + /** + * @param {string} type + * @param {EventTargetCallback|EventListener} listener + * @returns {NodeEventTarget} + */ + once(type, listener) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + this.addEventListener(type, listener, { + once: true, + [kIsNodeStyleListener]: true, + }); + return this; + } + + /** + * @param {string} type + * @returns {NodeEventTarget} + */ + removeAllListeners(type) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + if (type !== undefined) { + this[kEvents].delete(String(type)); + } else { + this[kEvents].clear(); + } + + return this; + } +} + +Object.defineProperties(NodeEventTarget.prototype, { + setMaxListeners: kEnumerableProperty, + getMaxListeners: kEnumerableProperty, + eventNames: kEnumerableProperty, + listenerCount: kEnumerableProperty, + off: kEnumerableProperty, + removeListener: kEnumerableProperty, + on: kEnumerableProperty, + addListener: kEnumerableProperty, + once: kEnumerableProperty, + emit: kEnumerableProperty, + removeAllListeners: kEnumerableProperty, +}); + +// EventTarget API + +function shouldAddListener(listener) { + if ( + typeof listener === "function" || + typeof listener?.handleEvent === "function" + ) { + return true; + } + + if (listener == null) { + return false; + } + + throw new ERR_INVALID_ARG_TYPE("listener", "EventListener", listener); +} + +function validateEventListenerOptions(options) { + if (typeof options === "boolean") { + return { capture: options }; + } + + if (options === null) { + return {}; + } + validateObject(options, "options", { + allowArray: true, + allowFunction: true, + }); + return { + once: Boolean(options.once), + capture: Boolean(options.capture), + passive: Boolean(options.passive), + signal: options.signal, + weak: options[kWeakHandler], + isNodeStyleListener: Boolean(options[kIsNodeStyleListener]), + }; +} + +function isEventTarget(obj) { + return obj instanceof globalThis.EventTarget; +} + +function isNodeEventTarget(obj) { + return obj?.constructor?.[kIsNodeEventTarget]; +} + +function addCatch(promise) { + const then = promise.then; + if (typeof then === "function") { + then.call(promise, undefined, function (err) { + // The callback is called with nextTick to avoid a follow-up + // rejection from this promise. + emitUncaughtException(err); + }); + } +} + +function emitUncaughtException(err) { + nextTick(() => { + throw err; + }); +} + +function makeEventHandler(handler) { + // Event handlers are dispatched in the order they were first set + // See https://github.com/nodejs/node/pull/35949#issuecomment-722496598 + function eventHandler(...args) { + if (typeof eventHandler.handler !== "function") { + return; + } + return Reflect.apply(eventHandler.handler, this, args); + } + eventHandler.handler = handler; + return eventHandler; +} + +function defineEventHandler(emitter, name) { + // 8.1.5.1 Event handlers - basically `on[eventName]` attributes + Object.defineProperty(emitter, `on${name}`, { + get() { + return this[kHandlers]?.get(name)?.handler ?? null; + }, + set(value) { + if (!this[kHandlers]) { + this[kHandlers] = new Map(); + } + let wrappedHandler = this[kHandlers]?.get(name); + if (wrappedHandler) { + if (typeof wrappedHandler.handler === "function") { + this[kEvents].get(name).size--; + const size = this[kEvents].get(name).size; + this[kRemoveListener](size, name, wrappedHandler.handler, false); + } + wrappedHandler.handler = value; + if (typeof wrappedHandler.handler === "function") { + this[kEvents].get(name).size++; + const size = this[kEvents].get(name).size; + this[kNewListener](size, name, value, false, false, false, false); + } + } else { + wrappedHandler = makeEventHandler(value); + this.addEventListener(name, wrappedHandler); + } + this[kHandlers].set(name, wrappedHandler); + }, + configurable: true, + enumerable: true, + }); +} + +const EventEmitterMixin = (Superclass) => { + class MixedEventEmitter extends Superclass { + constructor(...args) { + super(...args); + EventEmitter.call(this); + } + } + const protoProps = Object.getOwnPropertyDescriptors(EventEmitter.prototype); + delete protoProps.constructor; + Object.defineProperties(MixedEventEmitter.prototype, protoProps); + return MixedEventEmitter; +}; + +export { + CustomEvent, + defineEventHandler, + Event, + EventEmitterMixin, + EventTarget, + initEventTarget, + initNodeEventTarget, + isEventTarget, + kCreateEvent, + kEvents, + kNewListener, + kRemoveListener, + kTrustEvent, + kWeakHandler, + NodeEventTarget, +}; + +export default { + CustomEvent, + Event, + EventEmitterMixin, + EventTarget, + NodeEventTarget, + defineEventHandler, + initEventTarget, + initNodeEventTarget, + kCreateEvent, + kNewListener, + kTrustEvent, + kRemoveListener, + kEvents, + kWeakHandler, + isEventTarget, +}; diff --git a/ext/node/polyfills/internal/fixed_queue.ts b/ext/node/polyfills/internal/fixed_queue.ts new file mode 100644 index 000000000..e6b8db70f --- /dev/null +++ b/ext/node/polyfills/internal/fixed_queue.ts @@ -0,0 +1,123 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. + +// Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two. +const kSize = 2048; +const kMask = kSize - 1; + +// The FixedQueue is implemented as a singly-linked list of fixed-size +// circular buffers. It looks something like this: +// +// head tail +// | | +// v v +// +-----------+ <-----\ +-----------+ <------\ +-----------+ +// | [null] | \----- | next | \------- | next | +// +-----------+ +-----------+ +-----------+ +// | item | <-- bottom | item | <-- bottom | [empty] | +// | item | | item | | [empty] | +// | item | | item | | [empty] | +// | item | | item | | [empty] | +// | item | | item | bottom --> | item | +// | item | | item | | item | +// | ... | | ... | | ... | +// | item | | item | | item | +// | item | | item | | item | +// | [empty] | <-- top | item | | item | +// | [empty] | | item | | item | +// | [empty] | | [empty] | <-- top top --> | [empty] | +// +-----------+ +-----------+ +-----------+ +// +// Or, if there is only one circular buffer, it looks something +// like either of these: +// +// head tail head tail +// | | | | +// v v v v +// +-----------+ +-----------+ +// | [null] | | [null] | +// +-----------+ +-----------+ +// | [empty] | | item | +// | [empty] | | item | +// | item | <-- bottom top --> | [empty] | +// | item | | [empty] | +// | [empty] | <-- top bottom --> | item | +// | [empty] | | item | +// +-----------+ +-----------+ +// +// Adding a value means moving `top` forward by one, removing means +// moving `bottom` forward by one. After reaching the end, the queue +// wraps around. +// +// When `top === bottom` the current queue is empty and when +// `top + 1 === bottom` it's full. This wastes a single space of storage +// but allows much quicker checks. + +class FixedCircularBuffer { + bottom: number; + top: number; + list: undefined | Array<unknown>; + next: FixedCircularBuffer | null; + + constructor() { + this.bottom = 0; + this.top = 0; + this.list = new Array(kSize); + this.next = null; + } + + isEmpty() { + return this.top === this.bottom; + } + + isFull() { + return ((this.top + 1) & kMask) === this.bottom; + } + + push(data: unknown) { + this.list![this.top] = data; + this.top = (this.top + 1) & kMask; + } + + shift() { + const nextItem = this.list![this.bottom]; + if (nextItem === undefined) { + return null; + } + this.list![this.bottom] = undefined; + this.bottom = (this.bottom + 1) & kMask; + return nextItem; + } +} + +export class FixedQueue { + head: FixedCircularBuffer; + tail: FixedCircularBuffer; + + constructor() { + this.head = this.tail = new FixedCircularBuffer(); + } + + isEmpty() { + return this.head.isEmpty(); + } + + push(data: unknown) { + if (this.head.isFull()) { + // Head is full: Creates a new queue, sets the old queue's `.next` to it, + // and sets it as the new main queue. + this.head = this.head.next = new FixedCircularBuffer(); + } + this.head.push(data); + } + + shift() { + const tail = this.tail; + const next = tail.shift(); + if (tail.isEmpty() && tail.next !== null) { + // If there is another queue, it forms the new tail. + this.tail = tail.next; + } + return next; + } +} diff --git a/ext/node/polyfills/internal/freelist.ts b/ext/node/polyfills/internal/freelist.ts new file mode 100644 index 000000000..8faba8e68 --- /dev/null +++ b/ext/node/polyfills/internal/freelist.ts @@ -0,0 +1,30 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +type Fn<T> = (...args: unknown[]) => T; +export class FreeList<T> { + name: string; + ctor: Fn<T>; + max: number; + list: Array<T>; + constructor(name: string, max: number, ctor: Fn<T>) { + this.name = name; + this.ctor = ctor; + this.max = max; + this.list = []; + } + + alloc(): T { + return this.list.length > 0 + ? this.list.pop() + : Reflect.apply(this.ctor, this, arguments); + } + + free(obj: T) { + if (this.list.length < this.max) { + this.list.push(obj); + return true; + } + return false; + } +} diff --git a/ext/node/polyfills/internal/fs/streams.d.ts b/ext/node/polyfills/internal/fs/streams.d.ts new file mode 100644 index 000000000..9e70c2431 --- /dev/null +++ b/ext/node/polyfills/internal/fs/streams.d.ts @@ -0,0 +1,326 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright DefinitelyTyped contributors. All rights reserved. MIT license. +// deno-lint-ignore-file no-explicit-any + +import * as stream from "internal:deno_node/polyfills/_stream.d.ts"; +import * as promises from "internal:deno_node/polyfills/fs/promises.ts"; + +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + BufferEncoding, + ErrnoException, +} from "internal:deno_node/polyfills/_global.d.ts"; + +type PathLike = string | Buffer | URL; + +/** + * Instances of `fs.ReadStream` are created and returned using the {@link createReadStream} function. + * @since v0.1.93 + */ +export class ReadStream extends stream.Readable { + close(callback?: (err?: ErrnoException | null) => void): void; + /** + * The number of bytes that have been read so far. + * @since v6.4.0 + */ + bytesRead: number; + /** + * The path to the file the stream is reading from as specified in the first + * argument to `fs.createReadStream()`. If `path` is passed as a string, then`readStream.path` will be a string. If `path` is passed as a `Buffer`, then`readStream.path` will be a + * `Buffer`. If `fd` is specified, then`readStream.path` will be `undefined`. + * @since v0.1.93 + */ + path: string | Buffer; + /** + * This property is `true` if the underlying file has not been opened yet, + * i.e. before the `'ready'` event is emitted. + * @since v11.2.0, v10.16.0 + */ + pending: boolean; + /** + * events.EventEmitter + * 1. open + * 2. close + * 3. ready + */ + addListener(event: "close", listener: () => void): this; + addListener(event: "data", listener: (chunk: Buffer | string) => void): this; + addListener(event: "end", listener: () => void): this; + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: "open", listener: (fd: number) => void): this; + addListener(event: "pause", listener: () => void): this; + addListener(event: "readable", listener: () => void): this; + addListener(event: "ready", listener: () => void): this; + addListener(event: "resume", listener: () => void): this; + addListener(event: string | symbol, listener: (...args: any[]) => void): this; + on(event: "close", listener: () => void): this; + on(event: "data", listener: (chunk: Buffer | string) => void): this; + on(event: "end", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: "open", listener: (fd: number) => void): this; + on(event: "pause", listener: () => void): this; + on(event: "readable", listener: () => void): this; + on(event: "ready", listener: () => void): this; + on(event: "resume", listener: () => void): this; + on(event: string | symbol, listener: (...args: any[]) => void): this; + once(event: "close", listener: () => void): this; + once(event: "data", listener: (chunk: Buffer | string) => void): this; + once(event: "end", listener: () => void): this; + once(event: "error", listener: (err: Error) => void): this; + once(event: "open", listener: (fd: number) => void): this; + once(event: "pause", listener: () => void): this; + once(event: "readable", listener: () => void): this; + once(event: "ready", listener: () => void): this; + once(event: "resume", listener: () => void): this; + once(event: string | symbol, listener: (...args: any[]) => void): this; + prependListener(event: "close", listener: () => void): this; + prependListener( + event: "data", + listener: (chunk: Buffer | string) => void, + ): this; + prependListener(event: "end", listener: () => void): this; + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: "open", listener: (fd: number) => void): this; + prependListener(event: "pause", listener: () => void): this; + prependListener(event: "readable", listener: () => void): this; + prependListener(event: "ready", listener: () => void): this; + prependListener(event: "resume", listener: () => void): this; + prependListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): this; + prependOnceListener(event: "close", listener: () => void): this; + prependOnceListener( + event: "data", + listener: (chunk: Buffer | string) => void, + ): this; + prependOnceListener(event: "end", listener: () => void): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: "open", listener: (fd: number) => void): this; + prependOnceListener(event: "pause", listener: () => void): this; + prependOnceListener(event: "readable", listener: () => void): this; + prependOnceListener(event: "ready", listener: () => void): this; + prependOnceListener(event: "resume", listener: () => void): this; + prependOnceListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): this; +} +/** + * * Extends `stream.Writable` + * + * Instances of `fs.WriteStream` are created and returned using the {@link createWriteStream} function. + * @since v0.1.93 + */ +export class WriteStream extends stream.Writable { + /** + * Closes `writeStream`. Optionally accepts a + * callback that will be executed once the `writeStream`is closed. + * @since v0.9.4 + */ + close(callback?: (err?: ErrnoException | null) => void): void; + /** + * The number of bytes written so far. Does not include data that is still queued + * for writing. + * @since v0.4.7 + */ + bytesWritten: number; + /** + * The path to the file the stream is writing to as specified in the first + * argument to {@link createWriteStream}. If `path` is passed as a string, then`writeStream.path` will be a string. If `path` is passed as a `Buffer`, then`writeStream.path` will be a + * `Buffer`. + * @since v0.1.93 + */ + path: string | Buffer; + /** + * This property is `true` if the underlying file has not been opened yet, + * i.e. before the `'ready'` event is emitted. + * @since v11.2.0 + */ + pending: boolean; + /** + * events.EventEmitter + * 1. open + * 2. close + * 3. ready + */ + addListener(event: "close", listener: () => void): this; + addListener(event: "drain", listener: () => void): this; + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: "finish", listener: () => void): this; + addListener(event: "open", listener: (fd: number) => void): this; + addListener(event: "pipe", listener: (src: stream.Readable) => void): this; + addListener(event: "ready", listener: () => void): this; + addListener(event: "unpipe", listener: (src: stream.Readable) => void): this; + addListener(event: string | symbol, listener: (...args: any[]) => void): this; + on(event: "close", listener: () => void): this; + on(event: "drain", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: "finish", listener: () => void): this; + on(event: "open", listener: (fd: number) => void): this; + on(event: "pipe", listener: (src: stream.Readable) => void): this; + on(event: "ready", listener: () => void): this; + on(event: "unpipe", listener: (src: stream.Readable) => void): this; + on(event: string | symbol, listener: (...args: any[]) => void): this; + once(event: "close", listener: () => void): this; + once(event: "drain", listener: () => void): this; + once(event: "error", listener: (err: Error) => void): this; + once(event: "finish", listener: () => void): this; + once(event: "open", listener: (fd: number) => void): this; + once(event: "pipe", listener: (src: stream.Readable) => void): this; + once(event: "ready", listener: () => void): this; + once(event: "unpipe", listener: (src: stream.Readable) => void): this; + once(event: string | symbol, listener: (...args: any[]) => void): this; + prependListener(event: "close", listener: () => void): this; + prependListener(event: "drain", listener: () => void): this; + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: "finish", listener: () => void): this; + prependListener(event: "open", listener: (fd: number) => void): this; + prependListener( + event: "pipe", + listener: (src: stream.Readable) => void, + ): this; + prependListener(event: "ready", listener: () => void): this; + prependListener( + event: "unpipe", + listener: (src: stream.Readable) => void, + ): this; + prependListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): this; + prependOnceListener(event: "close", listener: () => void): this; + prependOnceListener(event: "drain", listener: () => void): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: "finish", listener: () => void): this; + prependOnceListener(event: "open", listener: (fd: number) => void): this; + prependOnceListener( + event: "pipe", + listener: (src: stream.Readable) => void, + ): this; + prependOnceListener(event: "ready", listener: () => void): this; + prependOnceListener( + event: "unpipe", + listener: (src: stream.Readable) => void, + ): this; + prependOnceListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): this; +} +interface StreamOptions { + flags?: string | undefined; + encoding?: BufferEncoding | undefined; + // @ts-ignore promises.FileHandle is not implemented + fd?: number | promises.FileHandle | undefined; + mode?: number | undefined; + autoClose?: boolean | undefined; + /** + * @default false + */ + emitClose?: boolean | undefined; + start?: number | undefined; + highWaterMark?: number | undefined; +} +interface ReadStreamOptions extends StreamOptions { + end?: number | undefined; +} +/** + * Unlike the 16 kb default `highWaterMark` for a `stream.Readable`, the stream + * returned by this method has a default `highWaterMark` of 64 kb. + * + * `options` can include `start` and `end` values to read a range of bytes from + * the file instead of the entire file. Both `start` and `end` are inclusive and + * start counting at 0, allowed values are in the + * \[0, [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\] range. If `fd` is specified and `start` is + * omitted or `undefined`, `fs.createReadStream()` reads sequentially from the + * current file position. The `encoding` can be any one of those accepted by `Buffer`. + * + * If `fd` is specified, `ReadStream` will ignore the `path` argument and will use + * the specified file descriptor. This means that no `'open'` event will be + * emitted. `fd` should be blocking; non-blocking `fd`s should be passed to `net.Socket`. + * + * If `fd` points to a character device that only supports blocking reads + * (such as keyboard or sound card), read operations do not finish until data is + * available. This can prevent the process from exiting and the stream from + * closing naturally. + * + * By default, the stream will emit a `'close'` event after it has been + * destroyed. Set the `emitClose` option to `false` to change this behavior. + * + * By providing the `fs` option, it is possible to override the corresponding `fs`implementations for `open`, `read`, and `close`. When providing the `fs` option, + * an override for `read` is required. If no `fd` is provided, an override for`open` is also required. If `autoClose` is `true`, an override for `close` is + * also required. + * + * ```js + * import { createReadStream } from "internal:deno_node/polyfills/internal/fs/fs"; + * + * // Create a stream from some character device. + * const stream = createReadStream('/dev/input/event0'); + * setTimeout(() => { + * stream.close(); // This may not close the stream. + * // Artificially marking end-of-stream, as if the underlying resource had + * // indicated end-of-file by itself, allows the stream to close. + * // This does not cancel pending read operations, and if there is such an + * // operation, the process may still not be able to exit successfully + * // until it finishes. + * stream.push(null); + * stream.read(0); + * }, 100); + * ``` + * + * If `autoClose` is false, then the file descriptor won't be closed, even if + * there's an error. It is the application's responsibility to close it and make + * sure there's no file descriptor leak. If `autoClose` is set to true (default + * behavior), on `'error'` or `'end'` the file descriptor will be closed + * automatically. + * + * `mode` sets the file mode (permission and sticky bits), but only if the + * file was created. + * + * An example to read the last 10 bytes of a file which is 100 bytes long: + * + * ```js + * import { createReadStream } from "internal:deno_node/polyfills/internal/fs/fs"; + * + * createReadStream('sample.txt', { start: 90, end: 99 }); + * ``` + * + * If `options` is a string, then it specifies the encoding. + * @since v0.1.31 + */ +export function createReadStream( + path: PathLike, + options?: BufferEncoding | ReadStreamOptions, +): ReadStream; +/** + * `options` may also include a `start` option to allow writing data at some + * position past the beginning of the file, allowed values are in the + * \[0, [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\] range. Modifying a file rather than + * replacing it may require the `flags` option to be set to `r+` rather than the + * default `w`. The `encoding` can be any one of those accepted by `Buffer`. + * + * If `autoClose` is set to true (default behavior) on `'error'` or `'finish'`the file descriptor will be closed automatically. If `autoClose` is false, + * then the file descriptor won't be closed, even if there's an error. + * It is the application's responsibility to close it and make sure there's no + * file descriptor leak. + * + * By default, the stream will emit a `'close'` event after it has been + * destroyed. Set the `emitClose` option to `false` to change this behavior. + * + * By providing the `fs` option it is possible to override the corresponding `fs`implementations for `open`, `write`, `writev` and `close`. Overriding `write()`without `writev()` can reduce + * performance as some optimizations (`_writev()`) + * will be disabled. When providing the `fs` option, overrides for at least one of`write` and `writev` are required. If no `fd` option is supplied, an override + * for `open` is also required. If `autoClose` is `true`, an override for `close`is also required. + * + * Like `fs.ReadStream`, if `fd` is specified, `fs.WriteStream` will ignore the`path` argument and will use the specified file descriptor. This means that no`'open'` event will be + * emitted. `fd` should be blocking; non-blocking `fd`s + * should be passed to `net.Socket`. + * + * If `options` is a string, then it specifies the encoding. + * @since v0.1.31 + */ +export function createWriteStream( + path: PathLike, + options?: BufferEncoding | StreamOptions, +): WriteStream; diff --git a/ext/node/polyfills/internal/fs/streams.mjs b/ext/node/polyfills/internal/fs/streams.mjs new file mode 100644 index 000000000..4d751df76 --- /dev/null +++ b/ext/node/polyfills/internal/fs/streams.mjs @@ -0,0 +1,494 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { kEmptyObject } from "internal:deno_node/polyfills/internal/util.mjs"; +import { deprecate } from "internal:deno_node/polyfills/util.ts"; +import { validateFunction, validateInteger } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { errorOrDestroy } from "internal:deno_node/polyfills/internal/streams/destroy.mjs"; +import { open as fsOpen } from "internal:deno_node/polyfills/_fs/_fs_open.ts"; +import { read as fsRead } from "internal:deno_node/polyfills/_fs/_fs_read.ts"; +import { write as fsWrite } from "internal:deno_node/polyfills/_fs/_fs_write.mjs"; +import { writev as fsWritev } from "internal:deno_node/polyfills/_fs/_fs_writev.mjs"; +import { close as fsClose } from "internal:deno_node/polyfills/_fs/_fs_close.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + copyObject, + getOptions, + getValidatedFd, + validatePath, +} from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import { finished, Readable, Writable } from "internal:deno_node/polyfills/stream.ts"; +import { toPathIfFileURL } from "internal:deno_node/polyfills/internal/url.ts"; +import { nextTick } from "internal:deno_node/polyfills/_next_tick.ts"; +const kIoDone = Symbol("kIoDone"); +const kIsPerformingIO = Symbol("kIsPerformingIO"); + +const kFs = Symbol("kFs"); + +function _construct(callback) { + // deno-lint-ignore no-this-alias + const stream = this; + if (typeof stream.fd === "number") { + callback(); + return; + } + + if (stream.open !== openWriteFs && stream.open !== openReadFs) { + // Backwards compat for monkey patching open(). + const orgEmit = stream.emit; + stream.emit = function (...args) { + if (args[0] === "open") { + this.emit = orgEmit; + callback(); + Reflect.apply(orgEmit, this, args); + } else if (args[0] === "error") { + this.emit = orgEmit; + callback(args[1]); + } else { + Reflect.apply(orgEmit, this, args); + } + }; + stream.open(); + } else { + stream[kFs].open( + stream.path.toString(), + stream.flags, + stream.mode, + (er, fd) => { + if (er) { + callback(er); + } else { + stream.fd = fd; + callback(); + stream.emit("open", stream.fd); + stream.emit("ready"); + } + }, + ); + } +} + +function close(stream, err, cb) { + if (!stream.fd) { + cb(err); + } else { + stream[kFs].close(stream.fd, (er) => { + cb(er || err); + }); + stream.fd = null; + } +} + +function importFd(stream, options) { + if (typeof options.fd === "number") { + // When fd is a raw descriptor, we must keep our fingers crossed + // that the descriptor won't get closed, or worse, replaced with + // another one + // https://github.com/nodejs/node/issues/35862 + if (stream instanceof ReadStream) { + stream[kFs] = options.fs || { read: fsRead, close: fsClose }; + } + if (stream instanceof WriteStream) { + stream[kFs] = options.fs || + { write: fsWrite, writev: fsWritev, close: fsClose }; + } + return options.fd; + } + + throw new ERR_INVALID_ARG_TYPE("options.fd", ["number"], options.fd); +} + +export function ReadStream(path, options) { + if (!(this instanceof ReadStream)) { + return new ReadStream(path, options); + } + + // A little bit bigger buffer and water marks by default + options = copyObject(getOptions(options, kEmptyObject)); + if (options.highWaterMark === undefined) { + options.highWaterMark = 64 * 1024; + } + + if (options.autoDestroy === undefined) { + options.autoDestroy = false; + } + + if (options.fd == null) { + this.fd = null; + this[kFs] = options.fs || { open: fsOpen, read: fsRead, close: fsClose }; + validateFunction(this[kFs].open, "options.fs.open"); + + // Path will be ignored when fd is specified, so it can be falsy + this.path = toPathIfFileURL(path); + this.flags = options.flags === undefined ? "r" : options.flags; + this.mode = options.mode === undefined ? 0o666 : options.mode; + + validatePath(this.path); + } else { + this.fd = getValidatedFd(importFd(this, options)); + } + + options.autoDestroy = options.autoClose === undefined + ? true + : options.autoClose; + + validateFunction(this[kFs].read, "options.fs.read"); + + if (options.autoDestroy) { + validateFunction(this[kFs].close, "options.fs.close"); + } + + this.start = options.start; + this.end = options.end ?? Infinity; + this.pos = undefined; + this.bytesRead = 0; + this[kIsPerformingIO] = false; + + if (this.start !== undefined) { + validateInteger(this.start, "start", 0); + + this.pos = this.start; + } + + if (this.end !== Infinity) { + validateInteger(this.end, "end", 0); + + if (this.start !== undefined && this.start > this.end) { + throw new ERR_OUT_OF_RANGE( + "start", + `<= "end" (here: ${this.end})`, + this.start, + ); + } + } + + Reflect.apply(Readable, this, [options]); +} + +Object.setPrototypeOf(ReadStream.prototype, Readable.prototype); +Object.setPrototypeOf(ReadStream, Readable); + +Object.defineProperty(ReadStream.prototype, "autoClose", { + get() { + return this._readableState.autoDestroy; + }, + set(val) { + this._readableState.autoDestroy = val; + }, +}); + +const openReadFs = deprecate( + function () { + // Noop. + }, + "ReadStream.prototype.open() is deprecated", + "DEP0135", +); +ReadStream.prototype.open = openReadFs; + +ReadStream.prototype._construct = _construct; + +ReadStream.prototype._read = async function (n) { + n = this.pos !== undefined + ? Math.min(this.end - this.pos + 1, n) + : Math.min(this.end - this.bytesRead + 1, n); + + if (n <= 0) { + this.push(null); + return; + } + + const buf = Buffer.allocUnsafeSlow(n); + + let error = null; + let bytesRead = null; + let buffer = undefined; + + this[kIsPerformingIO] = true; + + await new Promise((resolve) => { + this[kFs] + .read( + this.fd, + buf, + 0, + n, + this.pos ?? null, + (_er, _bytesRead, _buf) => { + error = _er; + bytesRead = _bytesRead; + buffer = _buf; + return resolve(true); + }, + ); + }); + + this[kIsPerformingIO] = false; + + // Tell ._destroy() that it's safe to close the fd now. + if (this.destroyed) { + this.emit(kIoDone, error); + return; + } + + if (error) { + errorOrDestroy(this, error); + } else if ( + typeof bytesRead === "number" && + bytesRead > 0 + ) { + if (this.pos !== undefined) { + this.pos += bytesRead; + } + + this.bytesRead += bytesRead; + + if (bytesRead !== buffer.length) { + // Slow path. Shrink to fit. + // Copy instead of slice so that we don't retain + // large backing buffer for small reads. + const dst = Buffer.allocUnsafeSlow(bytesRead); + buffer.copy(dst, 0, 0, bytesRead); + buffer = dst; + } + + this.push(buffer); + } else { + this.push(null); + } +}; + +ReadStream.prototype._destroy = function (err, cb) { + // Usually for async IO it is safe to close a file descriptor + // even when there are pending operations. However, due to platform + // differences file IO is implemented using synchronous operations + // running in a thread pool. Therefore, file descriptors are not safe + // to close while used in a pending read or write operation. Wait for + // any pending IO (kIsPerformingIO) to complete (kIoDone). + if (this[kIsPerformingIO]) { + this.once(kIoDone, (er) => close(this, err || er, cb)); + } else { + close(this, err, cb); + } +}; + +ReadStream.prototype.close = function (cb) { + if (typeof cb === "function") finished(this, cb); + this.destroy(); +}; + +Object.defineProperty(ReadStream.prototype, "pending", { + get() { + return this.fd === null; + }, + configurable: true, +}); + +export function WriteStream(path, options) { + if (!(this instanceof WriteStream)) { + return new WriteStream(path, options); + } + + options = copyObject(getOptions(options, kEmptyObject)); + + // Only buffers are supported. + options.decodeStrings = true; + + if (options.fd == null) { + this.fd = null; + this[kFs] = options.fs || + { open: fsOpen, write: fsWrite, writev: fsWritev, close: fsClose }; + validateFunction(this[kFs].open, "options.fs.open"); + + // Path will be ignored when fd is specified, so it can be falsy + this.path = toPathIfFileURL(path); + this.flags = options.flags === undefined ? "w" : options.flags; + this.mode = options.mode === undefined ? 0o666 : options.mode; + + validatePath(this.path); + } else { + this.fd = getValidatedFd(importFd(this, options)); + } + + options.autoDestroy = options.autoClose === undefined + ? true + : options.autoClose; + + if (!this[kFs].write && !this[kFs].writev) { + throw new ERR_INVALID_ARG_TYPE( + "options.fs.write", + "function", + this[kFs].write, + ); + } + + if (this[kFs].write) { + validateFunction(this[kFs].write, "options.fs.write"); + } + + if (this[kFs].writev) { + validateFunction(this[kFs].writev, "options.fs.writev"); + } + + if (options.autoDestroy) { + validateFunction(this[kFs].close, "options.fs.close"); + } + + // It's enough to override either, in which case only one will be used. + if (!this[kFs].write) { + this._write = null; + } + if (!this[kFs].writev) { + this._writev = null; + } + + this.start = options.start; + this.pos = undefined; + this.bytesWritten = 0; + this[kIsPerformingIO] = false; + + if (this.start !== undefined) { + validateInteger(this.start, "start", 0); + + this.pos = this.start; + } + + Reflect.apply(Writable, this, [options]); + + if (options.encoding) { + this.setDefaultEncoding(options.encoding); + } +} + +Object.setPrototypeOf(WriteStream.prototype, Writable.prototype); +Object.setPrototypeOf(WriteStream, Writable); + +Object.defineProperty(WriteStream.prototype, "autoClose", { + get() { + return this._writableState.autoDestroy; + }, + set(val) { + this._writableState.autoDestroy = val; + }, +}); + +const openWriteFs = deprecate( + function () { + // Noop. + }, + "WriteStream.prototype.open() is deprecated", + "DEP0135", +); +WriteStream.prototype.open = openWriteFs; + +WriteStream.prototype._construct = _construct; + +WriteStream.prototype._write = function (data, _encoding, cb) { + this[kIsPerformingIO] = true; + this[kFs].write(this.fd, data, 0, data.length, this.pos, (er, bytes) => { + this[kIsPerformingIO] = false; + if (this.destroyed) { + // Tell ._destroy() that it's safe to close the fd now. + cb(er); + return this.emit(kIoDone, er); + } + + if (er) { + return cb(er); + } + + this.bytesWritten += bytes; + cb(); + }); + + if (this.pos !== undefined) { + this.pos += data.length; + } +}; + +WriteStream.prototype._writev = function (data, cb) { + const len = data.length; + const chunks = new Array(len); + let size = 0; + + for (let i = 0; i < len; i++) { + const chunk = data[i].chunk; + + chunks[i] = chunk; + size += chunk.length; + } + + this[kIsPerformingIO] = true; + this[kFs].writev(this.fd, chunks, this.pos ?? null, (er, bytes) => { + this[kIsPerformingIO] = false; + if (this.destroyed) { + // Tell ._destroy() that it's safe to close the fd now. + cb(er); + return this.emit(kIoDone, er); + } + + if (er) { + return cb(er); + } + + this.bytesWritten += bytes; + cb(); + }); + + if (this.pos !== undefined) { + this.pos += size; + } +}; + +WriteStream.prototype._destroy = function (err, cb) { + // Usually for async IO it is safe to close a file descriptor + // even when there are pending operations. However, due to platform + // differences file IO is implemented using synchronous operations + // running in a thread pool. Therefore, file descriptors are not safe + // to close while used in a pending read or write operation. Wait for + // any pending IO (kIsPerformingIO) to complete (kIoDone). + if (this[kIsPerformingIO]) { + this.once(kIoDone, (er) => close(this, err || er, cb)); + } else { + close(this, err, cb); + } +}; + +WriteStream.prototype.close = function (cb) { + if (cb) { + if (this.closed) { + nextTick(cb); + return; + } + this.on("close", cb); + } + + // If we are not autoClosing, we should call + // destroy on 'finish'. + if (!this.autoClose) { + this.on("finish", this.destroy); + } + + // We use end() instead of destroy() because of + // https://github.com/nodejs/node/issues/2006 + this.end(); +}; + +// There is no shutdown() for files. +WriteStream.prototype.destroySoon = WriteStream.prototype.end; + +Object.defineProperty(WriteStream.prototype, "pending", { + get() { + return this.fd === null; + }, + configurable: true, +}); + +export function createReadStream(path, options) { + return new ReadStream(path, options); +} + +export function createWriteStream(path, options) { + return new WriteStream(path, options); +} diff --git a/ext/node/polyfills/internal/fs/utils.mjs b/ext/node/polyfills/internal/fs/utils.mjs new file mode 100644 index 000000000..9d74c5eee --- /dev/null +++ b/ext/node/polyfills/internal/fs/utils.mjs @@ -0,0 +1,1045 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +"use strict"; + +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + ERR_FS_EISDIR, + ERR_FS_INVALID_SYMLINK_TYPE, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_OUT_OF_RANGE, + hideStackFrames, + uvException, +} from "internal:deno_node/polyfills/internal/errors.ts"; + +import { + isArrayBufferView, + isBigUint64Array, + isDate, + isUint8Array, +} from "internal:deno_node/polyfills/internal/util/types.ts"; +import { once } from "internal:deno_node/polyfills/internal/util.mjs"; +import { deprecate } from "internal:deno_node/polyfills/util.ts"; +import { toPathIfFileURL } from "internal:deno_node/polyfills/internal/url.ts"; +import { + validateAbortSignal, + validateBoolean, + validateFunction, + validateInt32, + validateInteger, + validateObject, + validateUint32, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import pathModule from "internal:deno_node/polyfills/path.ts"; +const kType = Symbol("type"); +const kStats = Symbol("stats"); +import assert from "internal:deno_node/polyfills/internal/assert.mjs"; +import { lstat, lstatSync } from "internal:deno_node/polyfills/_fs/_fs_lstat.ts"; +import { stat, statSync } from "internal:deno_node/polyfills/_fs/_fs_stat.ts"; +import { isWindows } from "internal:deno_node/polyfills/_util/os.ts"; +import process from "internal:deno_node/polyfills/process.ts"; + +import { + fs as fsConstants, + os as osConstants, +} from "internal:deno_node/polyfills/internal_binding/constants.ts"; +const { + F_OK = 0, + W_OK = 0, + R_OK = 0, + X_OK = 0, + COPYFILE_EXCL, + COPYFILE_FICLONE, + COPYFILE_FICLONE_FORCE, + O_APPEND, + O_CREAT, + O_EXCL, + O_RDONLY, + O_RDWR, + O_SYNC, + O_TRUNC, + O_WRONLY, + S_IFBLK, + S_IFCHR, + S_IFDIR, + S_IFIFO, + S_IFLNK, + S_IFMT, + S_IFREG, + S_IFSOCK, + UV_FS_SYMLINK_DIR, + UV_FS_SYMLINK_JUNCTION, + UV_DIRENT_UNKNOWN, + UV_DIRENT_FILE, + UV_DIRENT_DIR, + UV_DIRENT_LINK, + UV_DIRENT_FIFO, + UV_DIRENT_SOCKET, + UV_DIRENT_CHAR, + UV_DIRENT_BLOCK, +} = fsConstants; + +// The access modes can be any of F_OK, R_OK, W_OK or X_OK. Some might not be +// available on specific systems. They can be used in combination as well +// (F_OK | R_OK | W_OK | X_OK). +const kMinimumAccessMode = Math.min(F_OK, W_OK, R_OK, X_OK); +const kMaximumAccessMode = F_OK | W_OK | R_OK | X_OK; + +const kDefaultCopyMode = 0; +// The copy modes can be any of COPYFILE_EXCL, COPYFILE_FICLONE or +// COPYFILE_FICLONE_FORCE. They can be used in combination as well +// (COPYFILE_EXCL | COPYFILE_FICLONE | COPYFILE_FICLONE_FORCE). +const kMinimumCopyMode = Math.min( + kDefaultCopyMode, + COPYFILE_EXCL, + COPYFILE_FICLONE, + COPYFILE_FICLONE_FORCE, +); +const kMaximumCopyMode = COPYFILE_EXCL | + COPYFILE_FICLONE | + COPYFILE_FICLONE_FORCE; + +// Most platforms don't allow reads or writes >= 2 GB. +// See https://github.com/libuv/libuv/pull/1501. +const kIoMaxLength = 2 ** 31 - 1; + +// Use 64kb in case the file type is not a regular file and thus do not know the +// actual file size. Increasing the value further results in more frequent over +// allocation for small files and consumes CPU time and memory that should be +// used else wise. +// Use up to 512kb per read otherwise to partition reading big files to prevent +// blocking other threads in case the available threads are all in use. +const kReadFileUnknownBufferLength = 64 * 1024; +const kReadFileBufferLength = 512 * 1024; + +const kWriteFileMaxChunkSize = 512 * 1024; + +export const kMaxUserId = 2 ** 32 - 1; + +export function assertEncoding(encoding) { + if (encoding && !Buffer.isEncoding(encoding)) { + const reason = "is invalid encoding"; + throw new ERR_INVALID_ARG_VALUE(encoding, "encoding", reason); + } +} + +export class Dirent { + constructor(name, type) { + this.name = name; + this[kType] = type; + } + + isDirectory() { + return this[kType] === UV_DIRENT_DIR; + } + + isFile() { + return this[kType] === UV_DIRENT_FILE; + } + + isBlockDevice() { + return this[kType] === UV_DIRENT_BLOCK; + } + + isCharacterDevice() { + return this[kType] === UV_DIRENT_CHAR; + } + + isSymbolicLink() { + return this[kType] === UV_DIRENT_LINK; + } + + isFIFO() { + return this[kType] === UV_DIRENT_FIFO; + } + + isSocket() { + return this[kType] === UV_DIRENT_SOCKET; + } +} + +class DirentFromStats extends Dirent { + constructor(name, stats) { + super(name, null); + this[kStats] = stats; + } +} + +for (const name of Reflect.ownKeys(Dirent.prototype)) { + if (name === "constructor") { + continue; + } + DirentFromStats.prototype[name] = function () { + return this[kStats][name](); + }; +} + +export function copyObject(source) { + const target = {}; + for (const key in source) { + target[key] = source[key]; + } + return target; +} + +const bufferSep = Buffer.from(pathModule.sep); + +function join(path, name) { + if ( + (typeof path === "string" || isUint8Array(path)) && + name === undefined + ) { + return path; + } + + if (typeof path === "string" && isUint8Array(name)) { + const pathBuffer = Buffer.from(pathModule.join(path, pathModule.sep)); + return Buffer.concat([pathBuffer, name]); + } + + if (typeof path === "string" && typeof name === "string") { + return pathModule.join(path, name); + } + + if (isUint8Array(path) && isUint8Array(name)) { + return Buffer.concat([path, bufferSep, name]); + } + + throw new ERR_INVALID_ARG_TYPE( + "path", + ["string", "Buffer"], + path, + ); +} + +export function getDirents(path, { 0: names, 1: types }, callback) { + let i; + if (typeof callback === "function") { + const len = names.length; + let toFinish = 0; + callback = once(callback); + for (i = 0; i < len; i++) { + const type = types[i]; + if (type === UV_DIRENT_UNKNOWN) { + const name = names[i]; + const idx = i; + toFinish++; + let filepath; + try { + filepath = join(path, name); + } catch (err) { + callback(err); + return; + } + lstat(filepath, (err, stats) => { + if (err) { + callback(err); + return; + } + names[idx] = new DirentFromStats(name, stats); + if (--toFinish === 0) { + callback(null, names); + } + }); + } else { + names[i] = new Dirent(names[i], types[i]); + } + } + if (toFinish === 0) { + callback(null, names); + } + } else { + const len = names.length; + for (i = 0; i < len; i++) { + names[i] = getDirent(path, names[i], types[i]); + } + return names; + } +} + +export function getDirent(path, name, type, callback) { + if (typeof callback === "function") { + if (type === UV_DIRENT_UNKNOWN) { + let filepath; + try { + filepath = join(path, name); + } catch (err) { + callback(err); + return; + } + lstat(filepath, (err, stats) => { + if (err) { + callback(err); + return; + } + callback(null, new DirentFromStats(name, stats)); + }); + } else { + callback(null, new Dirent(name, type)); + } + } else if (type === UV_DIRENT_UNKNOWN) { + const stats = lstatSync(join(path, name)); + return new DirentFromStats(name, stats); + } else { + return new Dirent(name, type); + } +} + +export function getOptions(options, defaultOptions) { + if ( + options === null || options === undefined || + typeof options === "function" + ) { + return defaultOptions; + } + + if (typeof options === "string") { + defaultOptions = { ...defaultOptions }; + defaultOptions.encoding = options; + options = defaultOptions; + } else if (typeof options !== "object") { + throw new ERR_INVALID_ARG_TYPE("options", ["string", "Object"], options); + } + + if (options.encoding !== "buffer") { + assertEncoding(options.encoding); + } + + if (options.signal !== undefined) { + validateAbortSignal(options.signal, "options.signal"); + } + return options; +} + +/** + * @param {InternalFSBinding.FSSyncContext} ctx + */ +export function handleErrorFromBinding(ctx) { + if (ctx.errno !== undefined) { // libuv error numbers + const err = uvException(ctx); + Error.captureStackTrace(err, handleErrorFromBinding); + throw err; + } + if (ctx.error !== undefined) { // Errors created in C++ land. + // TODO(joyeecheung): currently, ctx.error are encoding errors + // usually caused by memory problems. We need to figure out proper error + // code(s) for this. + Error.captureStackTrace(ctx.error, handleErrorFromBinding); + throw ctx.error; + } +} + +// Check if the path contains null types if it is a string nor Uint8Array, +// otherwise return silently. +export const nullCheck = hideStackFrames( + (path, propName, throwError = true) => { + const pathIsString = typeof path === "string"; + const pathIsUint8Array = isUint8Array(path); + + // We can only perform meaningful checks on strings and Uint8Arrays. + if ( + (!pathIsString && !pathIsUint8Array) || + (pathIsString && !path.includes("\u0000")) || + (pathIsUint8Array && !path.includes(0)) + ) { + return; + } + + const err = new ERR_INVALID_ARG_VALUE( + propName, + path, + "must be a string or Uint8Array without null bytes", + ); + if (throwError) { + throw err; + } + return err; + }, +); + +export function preprocessSymlinkDestination(path, type, linkPath) { + if (!isWindows) { + // No preprocessing is needed on Unix. + return path; + } + path = "" + path; + if (type === "junction") { + // Junctions paths need to be absolute and \\?\-prefixed. + // A relative target is relative to the link's parent directory. + path = pathModule.resolve(linkPath, "..", path); + return pathModule.toNamespacedPath(path); + } + if (pathModule.isAbsolute(path)) { + // If the path is absolute, use the \\?\-prefix to enable long filenames + return pathModule.toNamespacedPath(path); + } + // Windows symlinks don't tolerate forward slashes. + return path.replace(/\//g, "\\"); +} + +// Constructor for file stats. +function StatsBase( + dev, + mode, + nlink, + uid, + gid, + rdev, + blksize, + ino, + size, + blocks, +) { + this.dev = dev; + this.mode = mode; + this.nlink = nlink; + this.uid = uid; + this.gid = gid; + this.rdev = rdev; + this.blksize = blksize; + this.ino = ino; + this.size = size; + this.blocks = blocks; +} + +StatsBase.prototype.isDirectory = function () { + return this._checkModeProperty(S_IFDIR); +}; + +StatsBase.prototype.isFile = function () { + return this._checkModeProperty(S_IFREG); +}; + +StatsBase.prototype.isBlockDevice = function () { + return this._checkModeProperty(S_IFBLK); +}; + +StatsBase.prototype.isCharacterDevice = function () { + return this._checkModeProperty(S_IFCHR); +}; + +StatsBase.prototype.isSymbolicLink = function () { + return this._checkModeProperty(S_IFLNK); +}; + +StatsBase.prototype.isFIFO = function () { + return this._checkModeProperty(S_IFIFO); +}; + +StatsBase.prototype.isSocket = function () { + return this._checkModeProperty(S_IFSOCK); +}; + +const kNsPerMsBigInt = 10n ** 6n; +const kNsPerSecBigInt = 10n ** 9n; +const kMsPerSec = 10 ** 3; +const kNsPerMs = 10 ** 6; +function msFromTimeSpec(sec, nsec) { + return sec * kMsPerSec + nsec / kNsPerMs; +} + +function nsFromTimeSpecBigInt(sec, nsec) { + return sec * kNsPerSecBigInt + nsec; +} + +// The Date constructor performs Math.floor() to the timestamp. +// https://www.ecma-international.org/ecma-262/#sec-timeclip +// Since there may be a precision loss when the timestamp is +// converted to a floating point number, we manually round +// the timestamp here before passing it to Date(). +// Refs: https://github.com/nodejs/node/pull/12607 +function dateFromMs(ms) { + return new Date(Number(ms) + 0.5); +} + +export function BigIntStats( + dev, + mode, + nlink, + uid, + gid, + rdev, + blksize, + ino, + size, + blocks, + atimeNs, + mtimeNs, + ctimeNs, + birthtimeNs, +) { + Reflect.apply(StatsBase, this, [ + dev, + mode, + nlink, + uid, + gid, + rdev, + blksize, + ino, + size, + blocks, + ]); + + this.atimeMs = atimeNs / kNsPerMsBigInt; + this.mtimeMs = mtimeNs / kNsPerMsBigInt; + this.ctimeMs = ctimeNs / kNsPerMsBigInt; + this.birthtimeMs = birthtimeNs / kNsPerMsBigInt; + this.atimeNs = atimeNs; + this.mtimeNs = mtimeNs; + this.ctimeNs = ctimeNs; + this.birthtimeNs = birthtimeNs; + this.atime = dateFromMs(this.atimeMs); + this.mtime = dateFromMs(this.mtimeMs); + this.ctime = dateFromMs(this.ctimeMs); + this.birthtime = dateFromMs(this.birthtimeMs); +} + +Object.setPrototypeOf(BigIntStats.prototype, StatsBase.prototype); +Object.setPrototypeOf(BigIntStats, StatsBase); + +BigIntStats.prototype._checkModeProperty = function (property) { + if ( + isWindows && (property === S_IFIFO || property === S_IFBLK || + property === S_IFSOCK) + ) { + return false; // Some types are not available on Windows + } + return (this.mode & BigInt(S_IFMT)) === BigInt(property); +}; + +export function Stats( + dev, + mode, + nlink, + uid, + gid, + rdev, + blksize, + ino, + size, + blocks, + atimeMs, + mtimeMs, + ctimeMs, + birthtimeMs, +) { + StatsBase.call( + this, + dev, + mode, + nlink, + uid, + gid, + rdev, + blksize, + ino, + size, + blocks, + ); + this.atimeMs = atimeMs; + this.mtimeMs = mtimeMs; + this.ctimeMs = ctimeMs; + this.birthtimeMs = birthtimeMs; + this.atime = dateFromMs(atimeMs); + this.mtime = dateFromMs(mtimeMs); + this.ctime = dateFromMs(ctimeMs); + this.birthtime = dateFromMs(birthtimeMs); +} + +Object.setPrototypeOf(Stats.prototype, StatsBase.prototype); +Object.setPrototypeOf(Stats, StatsBase); + +// HACK: Workaround for https://github.com/standard-things/esm/issues/821. +// TODO(ronag): Remove this as soon as `esm` publishes a fixed version. +Stats.prototype.isFile = StatsBase.prototype.isFile; + +Stats.prototype._checkModeProperty = function (property) { + if ( + isWindows && (property === S_IFIFO || property === S_IFBLK || + property === S_IFSOCK) + ) { + return false; // Some types are not available on Windows + } + return (this.mode & S_IFMT) === property; +}; + +/** + * @param {Float64Array | BigUint64Array} stats + * @param {number} offset + * @returns + */ +export function getStatsFromBinding(stats, offset = 0) { + if (isBigUint64Array(stats)) { + return new BigIntStats( + stats[0 + offset], + stats[1 + offset], + stats[2 + offset], + stats[3 + offset], + stats[4 + offset], + stats[5 + offset], + stats[6 + offset], + stats[7 + offset], + stats[8 + offset], + stats[9 + offset], + nsFromTimeSpecBigInt(stats[10 + offset], stats[11 + offset]), + nsFromTimeSpecBigInt(stats[12 + offset], stats[13 + offset]), + nsFromTimeSpecBigInt(stats[14 + offset], stats[15 + offset]), + nsFromTimeSpecBigInt(stats[16 + offset], stats[17 + offset]), + ); + } + return new Stats( + stats[0 + offset], + stats[1 + offset], + stats[2 + offset], + stats[3 + offset], + stats[4 + offset], + stats[5 + offset], + stats[6 + offset], + stats[7 + offset], + stats[8 + offset], + stats[9 + offset], + msFromTimeSpec(stats[10 + offset], stats[11 + offset]), + msFromTimeSpec(stats[12 + offset], stats[13 + offset]), + msFromTimeSpec(stats[14 + offset], stats[15 + offset]), + msFromTimeSpec(stats[16 + offset], stats[17 + offset]), + ); +} + +export function stringToFlags(flags, name = "flags") { + if (typeof flags === "number") { + validateInt32(flags, name); + return flags; + } + + if (flags == null) { + return O_RDONLY; + } + + switch (flags) { + case "r": + return O_RDONLY; + case "rs": // Fall through. + case "sr": + return O_RDONLY | O_SYNC; + case "r+": + return O_RDWR; + case "rs+": // Fall through. + case "sr+": + return O_RDWR | O_SYNC; + + case "w": + return O_TRUNC | O_CREAT | O_WRONLY; + case "wx": // Fall through. + case "xw": + return O_TRUNC | O_CREAT | O_WRONLY | O_EXCL; + + case "w+": + return O_TRUNC | O_CREAT | O_RDWR; + case "wx+": // Fall through. + case "xw+": + return O_TRUNC | O_CREAT | O_RDWR | O_EXCL; + + case "a": + return O_APPEND | O_CREAT | O_WRONLY; + case "ax": // Fall through. + case "xa": + return O_APPEND | O_CREAT | O_WRONLY | O_EXCL; + case "as": // Fall through. + case "sa": + return O_APPEND | O_CREAT | O_WRONLY | O_SYNC; + + case "a+": + return O_APPEND | O_CREAT | O_RDWR; + case "ax+": // Fall through. + case "xa+": + return O_APPEND | O_CREAT | O_RDWR | O_EXCL; + case "as+": // Fall through. + case "sa+": + return O_APPEND | O_CREAT | O_RDWR | O_SYNC; + } + + throw new ERR_INVALID_ARG_VALUE("flags", flags); +} + +export const stringToSymlinkType = hideStackFrames((type) => { + let flags = 0; + if (typeof type === "string") { + switch (type) { + case "dir": + flags |= UV_FS_SYMLINK_DIR; + break; + case "junction": + flags |= UV_FS_SYMLINK_JUNCTION; + break; + case "file": + break; + default: + throw new ERR_FS_INVALID_SYMLINK_TYPE(type); + } + } + return flags; +}); + +// converts Date or number to a fractional UNIX timestamp +export function toUnixTimestamp(time, name = "time") { + // eslint-disable-next-line eqeqeq + if (typeof time === "string" && +time == time) { + return +time; + } + if (Number.isFinite(time)) { + if (time < 0) { + return Date.now() / 1000; + } + return time; + } + if (isDate(time)) { + // Convert to 123.456 UNIX timestamp + return Date.getTime(time) / 1000; + } + throw new ERR_INVALID_ARG_TYPE(name, ["Date", "Time in seconds"], time); +} + +export const validateOffsetLengthRead = hideStackFrames( + (offset, length, bufferLength) => { + if (offset < 0) { + throw new ERR_OUT_OF_RANGE("offset", ">= 0", offset); + } + if (length < 0) { + throw new ERR_OUT_OF_RANGE("length", ">= 0", length); + } + if (offset + length > bufferLength) { + throw new ERR_OUT_OF_RANGE( + "length", + `<= ${bufferLength - offset}`, + length, + ); + } + }, +); + +export const validateOffsetLengthWrite = hideStackFrames( + (offset, length, byteLength) => { + if (offset > byteLength) { + throw new ERR_OUT_OF_RANGE("offset", `<= ${byteLength}`, offset); + } + + if (length > byteLength - offset) { + throw new ERR_OUT_OF_RANGE("length", `<= ${byteLength - offset}`, length); + } + + if (length < 0) { + throw new ERR_OUT_OF_RANGE("length", ">= 0", length); + } + + validateInt32(length, "length", 0); + }, +); + +export const validatePath = hideStackFrames((path, propName = "path") => { + if (typeof path !== "string" && !isUint8Array(path)) { + throw new ERR_INVALID_ARG_TYPE(propName, ["string", "Buffer", "URL"], path); + } + + const err = nullCheck(path, propName, false); + + if (err !== undefined) { + throw err; + } +}); + +export const getValidatedPath = hideStackFrames( + (fileURLOrPath, propName = "path") => { + const path = toPathIfFileURL(fileURLOrPath); + validatePath(path, propName); + return path; + }, +); + +export const getValidatedFd = hideStackFrames((fd, propName = "fd") => { + if (Object.is(fd, -0)) { + return 0; + } + + validateInt32(fd, propName, 0); + + return fd; +}); + +export const validateBufferArray = hideStackFrames( + (buffers, propName = "buffers") => { + if (!Array.isArray(buffers)) { + throw new ERR_INVALID_ARG_TYPE(propName, "ArrayBufferView[]", buffers); + } + + for (let i = 0; i < buffers.length; i++) { + if (!isArrayBufferView(buffers[i])) { + throw new ERR_INVALID_ARG_TYPE(propName, "ArrayBufferView[]", buffers); + } + } + + return buffers; + }, +); + +let nonPortableTemplateWarn = true; + +export function warnOnNonPortableTemplate(template) { + // Template strings passed to the mkdtemp() family of functions should not + // end with 'X' because they are handled inconsistently across platforms. + if (nonPortableTemplateWarn && template.endsWith("X")) { + process.emitWarning( + "mkdtemp() templates ending with X are not portable. " + + "For details see: https://nodejs.org/api/fs.html", + ); + nonPortableTemplateWarn = false; + } +} + +const defaultCpOptions = { + dereference: false, + errorOnExist: false, + filter: undefined, + force: true, + preserveTimestamps: false, + recursive: false, +}; + +const defaultRmOptions = { + recursive: false, + force: false, + retryDelay: 100, + maxRetries: 0, +}; + +const defaultRmdirOptions = { + retryDelay: 100, + maxRetries: 0, + recursive: false, +}; + +export const validateCpOptions = hideStackFrames((options) => { + if (options === undefined) { + return { ...defaultCpOptions }; + } + validateObject(options, "options"); + options = { ...defaultCpOptions, ...options }; + validateBoolean(options.dereference, "options.dereference"); + validateBoolean(options.errorOnExist, "options.errorOnExist"); + validateBoolean(options.force, "options.force"); + validateBoolean(options.preserveTimestamps, "options.preserveTimestamps"); + validateBoolean(options.recursive, "options.recursive"); + if (options.filter !== undefined) { + validateFunction(options.filter, "options.filter"); + } + return options; +}); + +export const validateRmOptions = hideStackFrames( + (path, options, expectDir, cb) => { + options = validateRmdirOptions(options, defaultRmOptions); + validateBoolean(options.force, "options.force"); + + stat(path, (err, stats) => { + if (err) { + if (options.force && err.code === "ENOENT") { + return cb(null, options); + } + return cb(err, options); + } + + if (expectDir && !stats.isDirectory()) { + return cb(false); + } + + if (stats.isDirectory() && !options.recursive) { + return cb( + new ERR_FS_EISDIR({ + code: "EISDIR", + message: "is a directory", + path, + syscall: "rm", + errno: osConstants.errno.EISDIR, + }), + ); + } + return cb(null, options); + }); + }, +); + +export const validateRmOptionsSync = hideStackFrames( + (path, options, expectDir) => { + options = validateRmdirOptions(options, defaultRmOptions); + validateBoolean(options.force, "options.force"); + + if (!options.force || expectDir || !options.recursive) { + const isDirectory = statSync(path, { throwIfNoEntry: !options.force }) + ?.isDirectory(); + + if (expectDir && !isDirectory) { + return false; + } + + if (isDirectory && !options.recursive) { + throw new ERR_FS_EISDIR({ + code: "EISDIR", + message: "is a directory", + path, + syscall: "rm", + errno: EISDIR, + }); + } + } + + return options; + }, +); + +let recursiveRmdirWarned = process.noDeprecation; +export function emitRecursiveRmdirWarning() { + if (!recursiveRmdirWarned) { + process.emitWarning( + "In future versions of Node.js, fs.rmdir(path, { recursive: true }) " + + "will be removed. Use fs.rm(path, { recursive: true }) instead", + "DeprecationWarning", + "DEP0147", + ); + recursiveRmdirWarned = true; + } +} + +export const validateRmdirOptions = hideStackFrames( + (options, defaults = defaultRmdirOptions) => { + if (options === undefined) { + return defaults; + } + validateObject(options, "options"); + + options = { ...defaults, ...options }; + + validateBoolean(options.recursive, "options.recursive"); + validateInt32(options.retryDelay, "options.retryDelay", 0); + validateUint32(options.maxRetries, "options.maxRetries"); + + return options; + }, +); + +export const getValidMode = hideStackFrames((mode, type) => { + let min = kMinimumAccessMode; + let max = kMaximumAccessMode; + let def = F_OK; + if (type === "copyFile") { + min = kMinimumCopyMode; + max = kMaximumCopyMode; + def = mode || kDefaultCopyMode; + } else { + assert(type === "access"); + } + if (mode == null) { + return def; + } + if (Number.isInteger(mode) && mode >= min && mode <= max) { + return mode; + } + if (typeof mode !== "number") { + throw new ERR_INVALID_ARG_TYPE("mode", "integer", mode); + } + throw new ERR_OUT_OF_RANGE( + "mode", + `an integer >= ${min} && <= ${max}`, + mode, + ); +}); + +export const validateStringAfterArrayBufferView = hideStackFrames( + (buffer, name) => { + if (typeof buffer === "string") { + return; + } + + if ( + typeof buffer === "object" && + buffer !== null && + typeof buffer.toString === "function" && + Object.prototype.hasOwnProperty.call(buffer, "toString") + ) { + return; + } + + throw new ERR_INVALID_ARG_TYPE( + name, + ["string", "Buffer", "TypedArray", "DataView"], + buffer, + ); + }, +); + +export const validatePosition = hideStackFrames((position) => { + if (typeof position === "number") { + validateInteger(position, "position"); + } else if (typeof position === "bigint") { + if (!(position >= -(2n ** 63n) && position <= 2n ** 63n - 1n)) { + throw new ERR_OUT_OF_RANGE( + "position", + `>= ${-(2n ** 63n)} && <= ${2n ** 63n - 1n}`, + position, + ); + } + } else { + throw new ERR_INVALID_ARG_TYPE("position", ["integer", "bigint"], position); + } +}); + +export const realpathCacheKey = Symbol("realpathCacheKey"); +export const constants = { + kIoMaxLength, + kMaxUserId, + kReadFileBufferLength, + kReadFileUnknownBufferLength, + kWriteFileMaxChunkSize, +}; + +export const showStringCoercionDeprecation = deprecate( + () => {}, + "Implicit coercion of objects with own toString property is deprecated.", + "DEP0162", +); + +export default { + constants, + assertEncoding, + BigIntStats, // for testing + copyObject, + Dirent, + emitRecursiveRmdirWarning, + getDirent, + getDirents, + getOptions, + getValidatedFd, + getValidatedPath, + getValidMode, + handleErrorFromBinding, + kMaxUserId, + nullCheck, + preprocessSymlinkDestination, + realpathCacheKey, + getStatsFromBinding, + showStringCoercionDeprecation, + stringToFlags, + stringToSymlinkType, + Stats, + toUnixTimestamp, + validateBufferArray, + validateCpOptions, + validateOffsetLengthRead, + validateOffsetLengthWrite, + validatePath, + validatePosition, + validateRmOptions, + validateRmOptionsSync, + validateRmdirOptions, + validateStringAfterArrayBufferView, + warnOnNonPortableTemplate, +}; diff --git a/ext/node/polyfills/internal/hide_stack_frames.ts b/ext/node/polyfills/internal/hide_stack_frames.ts new file mode 100644 index 000000000..e1a6e4c27 --- /dev/null +++ b/ext/node/polyfills/internal/hide_stack_frames.ts @@ -0,0 +1,16 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// deno-lint-ignore no-explicit-any +type GenericFunction = (...args: any[]) => any; + +/** This function removes unnecessary frames from Node.js core errors. */ +export function hideStackFrames<T extends GenericFunction = GenericFunction>( + fn: T, +): T { + // We rename the functions that will be hidden to cut off the stacktrace + // at the outermost one. + const hidden = "__node_internal_" + fn.name; + Object.defineProperty(fn, "name", { value: hidden }); + + return fn; +} diff --git a/ext/node/polyfills/internal/http.ts b/ext/node/polyfills/internal/http.ts new file mode 100644 index 000000000..f541039dc --- /dev/null +++ b/ext/node/polyfills/internal/http.ts @@ -0,0 +1,38 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { setUnrefTimeout } from "internal:deno_node/polyfills/timers.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +let utcCache: string | undefined; + +export function utcDate() { + if (!utcCache) cache(); + return utcCache; +} + +function cache() { + const d = new Date(); + utcCache = d.toUTCString(); + setUnrefTimeout(resetCache, 1000 - d.getMilliseconds()); +} + +function resetCache() { + utcCache = undefined; +} + +export function emitStatistics( + _statistics: { startTime: [number, number] } | null, +) { + notImplemented("internal/http.emitStatistics"); +} + +export const kOutHeaders = Symbol("kOutHeaders"); +export const kNeedDrain = Symbol("kNeedDrain"); + +export default { + utcDate, + emitStatistics, + kOutHeaders, + kNeedDrain, +}; diff --git a/ext/node/polyfills/internal/idna.ts b/ext/node/polyfills/internal/idna.ts new file mode 100644 index 000000000..f71c06eba --- /dev/null +++ b/ext/node/polyfills/internal/idna.ts @@ -0,0 +1,461 @@ +// Copyright 2018-2023 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. + +// Copyright Mathias Bynens <https://mathiasbynens.be/> + +// 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. + +// Adapted from https://github.com/mathiasbynens/punycode.js + +// TODO(cmorten): migrate punycode logic to "icu" internal binding and/or "url" +// internal module so there can be re-use within the "url" module etc. + +"use strict"; + +/** Highest positive signed 32-bit float value */ +const maxInt = 2147483647; // aka. 0x7FFFFFFF or 2^31-1 + +/** Bootstring parameters */ +const base = 36; +const tMin = 1; +const tMax = 26; +const skew = 38; +const damp = 700; +const initialBias = 72; +const initialN = 128; // 0x80 +const delimiter = "-"; // '\x2D' + +/** Regular expressions */ +export const regexPunycode = /^xn--/; +export const regexNonASCII = /[^\0-\x7E]/; // non-ASCII chars +const regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g; // RFC 3490 separators + +/** Error messages */ +const errors: Record<string, string> = { + "overflow": "Overflow: input needs wider integers to process", + "not-basic": "Illegal input >= 0x80 (not a basic code point)", + "invalid-input": "Invalid input", +}; + +/** Convenience shortcuts */ +const baseMinusTMin = base - tMin; +const floor = Math.floor; + +/** + * A generic error utility function. + * + * @param type The error type. + * @return Throws a `RangeError` with the applicable error message. + */ +function error(type: string) { + throw new RangeError(errors[type]); +} + +/** + * A simple `Array#map`-like wrapper to work with domain name strings or email + * addresses. + * + * @param domain The domain name or email address. + * @param callback The function that gets called for every + * character. + * @return A new string of characters returned by the callback + * function. + */ +function mapDomain(str: string, fn: (label: string) => string) { + const parts = str.split("@"); + let result = ""; + + if (parts.length > 1) { + // In email addresses, only the domain name should be punycoded. Leave + // the local part (i.e. everything up to `@`) intact. + result = parts[0] + "@"; + str = parts[1]; + } + + // Avoid `split(regex)` for IE8 compatibility. See #17. + str = str.replace(regexSeparators, "\x2E"); + const labels = str.split("."); + const encoded = labels.map(fn).join("."); + + return result + encoded; +} + +/** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + * + * @param str The Unicode input string (UCS-2). + * @return The new array of code points. + */ +function ucs2decode(str: string) { + const output = []; + let counter = 0; + const length = str.length; + + while (counter < length) { + const value = str.charCodeAt(counter++); + + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // It's a high surrogate, and there is a next character. + const extra = str.charCodeAt(counter++); + + if ((extra & 0xFC00) == 0xDC00) { // Low surrogate. + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // It's an unmatched surrogate; only append this code unit, in case the + // next code unit is the high surrogate of a surrogate pair. + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + + return output; +} + +/** + * Creates a string based on an array of numeric code points. + * @see `punycode.ucs2.decode` + * @memberOf punycode.ucs2 + * @name encode + * @param codePoints The array of numeric code points. + * @returns The new Unicode string (UCS-2). + */ +function ucs2encode(array: number[]) { + return String.fromCodePoint(...array); +} + +export const ucs2 = { + decode: ucs2decode, + encode: ucs2encode, +}; + +/** + * Converts a basic code point into a digit/integer. + * @see `digitToBasic()` + * @private + * @param codePoint The basic numeric code point value. + * @returns The numeric value of a basic code point (for use in + * representing integers) in the range `0` to `base - 1`, or `base` if + * the code point does not represent a value. + */ +function basicToDigit(codePoint: number) { + if (codePoint - 0x30 < 0x0A) { + return codePoint - 0x16; + } + if (codePoint - 0x41 < 0x1A) { + return codePoint - 0x41; + } + if (codePoint - 0x61 < 0x1A) { + return codePoint - 0x61; + } + return base; +} + +/** + * Converts a digit/integer into a basic code point. + * + * @param digit The numeric value of a basic code point. + * @return The basic code point whose value (when used for + * representing integers) is `digit`, which needs to be in the range + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is + * used; else, the lowercase form is used. The behavior is undefined + * if `flag` is non-zero and `digit` has no uppercase form. + */ +function digitToBasic(digit: number, flag: number) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * Number(digit < 26) - (Number(flag != 0) << 5); +} + +/** + * Bias adaptation function as per section 3.4 of RFC 3492. + * https://tools.ietf.org/html/rfc3492#section-3.4 + */ +function adapt(delta: number, numPoints: number, firstTime: boolean) { + let k = 0; + delta = firstTime ? Math.floor(delta / damp) : delta >> 1; + delta += Math.floor(delta / numPoints); + + for (; /* no initialization */ delta > baseMinusTMin * tMax >> 1; k += base) { + delta = Math.floor(delta / baseMinusTMin); + } + + return Math.floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); +} + +/** + * Converts a Punycode string of ASCII-only symbols to a string of Unicode + * symbols. + * @memberOf punycode + * @param input The Punycode string of ASCII-only symbols. + * @returns The resulting string of Unicode symbols. + */ +export function decode(input: string): string { + // Don't use UCS-2. + const output = []; + const inputLength = input.length; + let i = 0; + let n = initialN; + let bias = initialBias; + + // Handle the basic code points: let `basic` be the number of input code + // points before the last delimiter, or `0` if there is none, then copy + // the first basic code points to the output. + + let basic = input.lastIndexOf(delimiter); + if (basic < 0) { + basic = 0; + } + + for (let j = 0; j < basic; ++j) { + // if it's not a basic code point + if (input.charCodeAt(j) >= 0x80) { + error("not-basic"); + } + output.push(input.charCodeAt(j)); + } + + // Main decoding loop: start just after the last delimiter if any basic code + // points were copied; start at the beginning otherwise. + + for ( + let index = basic > 0 ? basic + 1 : 0; + index < inputLength; + /* no final expression */ + ) { + // `index` is the index of the next character to be consumed. + // Decode a generalized variable-length integer into `delta`, + // which gets added to `i`. The overflow checking is easier + // if we increase `i` as we go, then subtract off its starting + // value at the end to obtain `delta`. + const oldi = i; + for (let w = 1, k = base;; /* no condition */ k += base) { + if (index >= inputLength) { + error("invalid-input"); + } + + const digit = basicToDigit(input.charCodeAt(index++)); + + if (digit >= base || digit > floor((maxInt - i) / w)) { + error("overflow"); + } + + i += digit * w; + const t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + + if (digit < t) { + break; + } + + const baseMinusT = base - t; + if (w > floor(maxInt / baseMinusT)) { + error("overflow"); + } + + w *= baseMinusT; + } + + const out = output.length + 1; + bias = adapt(i - oldi, out, oldi == 0); + + // `i` was supposed to wrap around from `out` to `0`, + // incrementing `n` each time, so we'll fix that now: + if (floor(i / out) > maxInt - n) { + error("overflow"); + } + + n += floor(i / out); + i %= out; + + // Insert `n` at position `i` of the output. + output.splice(i++, 0, n); + } + + return String.fromCodePoint(...output); +} + +/** + * Converts a string of Unicode symbols (e.g. a domain name label) to a + * Punycode string of ASCII-only symbols. + * + * @param str The string of Unicode symbols. + * @return The resulting Punycode string of ASCII-only symbols. + */ +export function encode(str: string) { + const output = []; + + // Convert the input in UCS-2 to an array of Unicode code points. + const input = ucs2decode(str); + + // Cache the length. + const inputLength = input.length; + + // Initialize the state. + let n = initialN; + let delta = 0; + let bias = initialBias; + + // Handle the basic code points. + for (const currentValue of input) { + if (currentValue < 0x80) { + output.push(String.fromCharCode(currentValue)); + } + } + + const basicLength = output.length; + let handledCPCount = basicLength; + + // `handledCPCount` is the number of code points that have been handled; + // `basicLength` is the number of basic code points. + + // Finish the basic string with a delimiter unless it's empty. + if (basicLength) { + output.push(delimiter); + } + + // Main encoding loop: + while (handledCPCount < inputLength) { + // All non-basic code points < n have been handled already. Find the next + // larger one: + let m = maxInt; + + for (const currentValue of input) { + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } + + // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>, + // but guard against overflow. + const handledCPCountPlusOne = handledCPCount + 1; + + if (m - n > Math.floor((maxInt - delta) / handledCPCountPlusOne)) { + error("overflow"); + } + + delta += (m - n) * handledCPCountPlusOne; + n = m; + + for (const currentValue of input) { + if (currentValue < n && ++delta > maxInt) { + error("overflow"); + } + + if (currentValue == n) { + // Represent delta as a generalized variable-length integer. + let q = delta; + + for (let k = base;; /* no condition */ k += base) { + const t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + + if (q < t) { + break; + } + + const qMinusT = q - t; + const baseMinusT = base - t; + + output.push( + String.fromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)), + ); + + q = Math.floor(qMinusT / baseMinusT); + } + + output.push(String.fromCharCode(digitToBasic(q, 0))); + + bias = adapt( + delta, + handledCPCountPlusOne, + handledCPCount == basicLength, + ); + + delta = 0; + ++handledCPCount; + } + } + + ++delta; + ++n; + } + + return output.join(""); +} + +/** + * Converts a Punycode string representing a domain name or an email address + * to Unicode. Only the Punycoded parts of the input will be converted, i.e. + * it doesn't matter if you call it on a string that has already been + * converted to Unicode. + * @memberOf punycode + * @param input The Punycoded domain name or email address to + * convert to Unicode. + * @returns The Unicode representation of the given Punycode + * string. + */ +export function toUnicode(input: string) { + return mapDomain(input, function (string) { + return regexPunycode.test(string) + ? decode(string.slice(4).toLowerCase()) + : string; + }); +} + +/** + * Converts a Unicode string representing a domain name or an email address to + * Punycode. Only the non-ASCII parts of the domain name will be converted, + * i.e. it doesn't matter if you call it with a domain that's already in + * ASCII. + * + * @param input The domain name or email address to convert, as a + * Unicode string. + * @return The Punycode representation of the given domain name or + * email address. + */ +export function toASCII(input: string): string { + return mapDomain(input, function (str: string) { + return regexNonASCII.test(str) ? "xn--" + encode(str) : str; + }); +} diff --git a/ext/node/polyfills/internal/net.ts b/ext/node/polyfills/internal/net.ts new file mode 100644 index 000000000..2e3a92dfd --- /dev/null +++ b/ext/node/polyfills/internal/net.ts @@ -0,0 +1,95 @@ +// Copyright 2018-2023 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 { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { uvException } from "internal:deno_node/polyfills/internal/errors.ts"; +import { writeBuffer } from "internal:deno_node/polyfills/internal_binding/node_file.ts"; + +// IPv4 Segment +const v4Seg = "(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"; +const v4Str = `(${v4Seg}[.]){3}${v4Seg}`; +const IPv4Reg = new RegExp(`^${v4Str}$`); + +// IPv6 Segment +const v6Seg = "(?:[0-9a-fA-F]{1,4})"; +const IPv6Reg = new RegExp( + "^(" + + `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` + + `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` + + `(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|` + + `(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|` + + `(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|` + + `(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|` + + `(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|` + + `(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` + + ")(%[0-9a-zA-Z-.:]{1,})?$", +); + +export function isIPv4(ip: string) { + return RegExp.prototype.test.call(IPv4Reg, ip); +} + +export function isIPv6(ip: string) { + return RegExp.prototype.test.call(IPv6Reg, ip); +} + +export function isIP(ip: string) { + if (isIPv4(ip)) { + return 4; + } + if (isIPv6(ip)) { + return 6; + } + + return 0; +} + +export function makeSyncWrite(fd: number) { + return function ( + // deno-lint-ignore no-explicit-any + this: any, + // deno-lint-ignore no-explicit-any + chunk: any, + enc: string, + cb: (err?: Error) => void, + ) { + if (enc !== "buffer") { + chunk = Buffer.from(chunk, enc); + } + + this._handle.bytesWritten += chunk.length; + + const ctx: { errno?: number } = {}; + writeBuffer(fd, chunk, 0, chunk.length, null, ctx); + + if (ctx.errno !== undefined) { + const ex = uvException(ctx); + ex.errno = ctx.errno; + + return cb(ex); + } + + cb(); + }; +} + +export const normalizedArgsSymbol = Symbol("normalizedArgs"); diff --git a/ext/node/polyfills/internal/normalize_encoding.mjs b/ext/node/polyfills/internal/normalize_encoding.mjs new file mode 100644 index 000000000..788c86c56 --- /dev/null +++ b/ext/node/polyfills/internal/normalize_encoding.mjs @@ -0,0 +1,72 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export function normalizeEncoding(enc) { + if (enc == null || enc === "utf8" || enc === "utf-8") return "utf8"; + return slowCases(enc); +} + +export function slowCases(enc) { + switch (enc.length) { + case 4: + if (enc === "UTF8") return "utf8"; + if (enc === "ucs2" || enc === "UCS2") return "utf16le"; + enc = `${enc}`.toLowerCase(); + if (enc === "utf8") return "utf8"; + if (enc === "ucs2") return "utf16le"; + break; + case 3: + if ( + enc === "hex" || enc === "HEX" || + `${enc}`.toLowerCase() === "hex" + ) { + return "hex"; + } + break; + case 5: + if (enc === "ascii") return "ascii"; + if (enc === "ucs-2") return "utf16le"; + if (enc === "UTF-8") return "utf8"; + if (enc === "ASCII") return "ascii"; + if (enc === "UCS-2") return "utf16le"; + enc = `${enc}`.toLowerCase(); + if (enc === "utf-8") return "utf8"; + if (enc === "ascii") return "ascii"; + if (enc === "ucs-2") return "utf16le"; + break; + case 6: + if (enc === "base64") return "base64"; + if (enc === "latin1" || enc === "binary") return "latin1"; + if (enc === "BASE64") return "base64"; + if (enc === "LATIN1" || enc === "BINARY") return "latin1"; + enc = `${enc}`.toLowerCase(); + if (enc === "base64") return "base64"; + if (enc === "latin1" || enc === "binary") return "latin1"; + break; + case 7: + if ( + enc === "utf16le" || enc === "UTF16LE" || + `${enc}`.toLowerCase() === "utf16le" + ) { + return "utf16le"; + } + break; + case 8: + if ( + enc === "utf-16le" || enc === "UTF-16LE" || + `${enc}`.toLowerCase() === "utf-16le" + ) { + return "utf16le"; + } + break; + case 9: + if ( + enc === "base64url" || enc === "BASE64URL" || + `${enc}`.toLowerCase() === "base64url" + ) { + return "base64url"; + } + break; + default: + if (enc === "") return "utf8"; + } +} diff --git a/ext/node/polyfills/internal/options.ts b/ext/node/polyfills/internal/options.ts new file mode 100644 index 000000000..68ccf0528 --- /dev/null +++ b/ext/node/polyfills/internal/options.ts @@ -0,0 +1,45 @@ +// Copyright 2018-2023 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 { getOptions } from "internal:deno_node/polyfills/internal_binding/node_options.ts"; + +let optionsMap: Map<string, { value: string }>; + +function getOptionsFromBinding() { + if (!optionsMap) { + ({ options: optionsMap } = getOptions()); + } + + return optionsMap; +} + +export function getOptionValue(optionName: string) { + const options = getOptionsFromBinding(); + + if (optionName.startsWith("--no-")) { + const option = options.get("--" + optionName.slice(5)); + + return option && !option.value; + } + + return options.get(optionName)?.value; +} diff --git a/ext/node/polyfills/internal/primordials.mjs b/ext/node/polyfills/internal/primordials.mjs new file mode 100644 index 000000000..1639efdb5 --- /dev/null +++ b/ext/node/polyfills/internal/primordials.mjs @@ -0,0 +1,30 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export const ArrayIsArray = Array.isArray; +export const ArrayPrototypeFilter = (that, ...args) => that.filter(...args); +export const ArrayPrototypeForEach = (that, ...args) => that.forEach(...args); +export const ArrayPrototypeIncludes = (that, ...args) => that.includes(...args); +export const ArrayPrototypeJoin = (that, ...args) => that.join(...args); +export const ArrayPrototypePush = (that, ...args) => that.push(...args); +export const ArrayPrototypeSlice = (that, ...args) => that.slice(...args); +export const ArrayPrototypeSome = (that, ...args) => that.some(...args); +export const ArrayPrototypeSort = (that, ...args) => that.sort(...args); +export const ArrayPrototypeUnshift = (that, ...args) => that.unshift(...args); +export const ObjectAssign = Object.assign; +export const ObjectCreate = Object.create; +export const ObjectPrototypeHasOwnProperty = Object.hasOwn; +export const RegExpPrototypeTest = (that, ...args) => that.test(...args); +export const RegExpPrototypeExec = RegExp.prototype.exec; +export const StringFromCharCode = String.fromCharCode; +export const StringPrototypeCharCodeAt = (that, ...args) => + that.charCodeAt(...args); +export const StringPrototypeEndsWith = (that, ...args) => + that.endsWith(...args); +export const StringPrototypeIncludes = (that, ...args) => + that.includes(...args); +export const StringPrototypeReplace = (that, ...args) => that.replace(...args); +export const StringPrototypeSlice = (that, ...args) => that.slice(...args); +export const StringPrototypeSplit = (that, ...args) => that.split(...args); +export const StringPrototypeStartsWith = (that, ...args) => + that.startsWith(...args); +export const StringPrototypeToUpperCase = (that) => that.toUpperCase(); diff --git a/ext/node/polyfills/internal/process/per_thread.mjs b/ext/node/polyfills/internal/process/per_thread.mjs new file mode 100644 index 000000000..3146083d1 --- /dev/null +++ b/ext/node/polyfills/internal/process/per_thread.mjs @@ -0,0 +1,272 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +const kInternal = Symbol("internal properties"); + +const replaceUnderscoresRegex = /_/g; +const leadingDashesRegex = /^--?/; +const trailingValuesRegex = /=.*$/; + +// This builds the initial process.allowedNodeEnvironmentFlags +// from data in the config binding. +export function buildAllowedFlags() { + const allowedNodeEnvironmentFlags = [ + "--track-heap-objects", + "--no-track-heap-objects", + "--node-snapshot", + "--no-node-snapshot", + "--require", + "--max-old-space-size", + "--trace-exit", + "--no-trace-exit", + "--disallow-code-generation-from-strings", + "--experimental-json-modules", + "--no-experimental-json-modules", + "--interpreted-frames-native-stack", + "--inspect-brk", + "--no-inspect-brk", + "--trace-tls", + "--no-trace-tls", + "--stack-trace-limit", + "--experimental-repl-await", + "--no-experimental-repl-await", + "--preserve-symlinks", + "--no-preserve-symlinks", + "--report-uncaught-exception", + "--no-report-uncaught-exception", + "--experimental-modules", + "--no-experimental-modules", + "--report-signal", + "--jitless", + "--inspect-port", + "--heapsnapshot-near-heap-limit", + "--tls-keylog", + "--force-context-aware", + "--no-force-context-aware", + "--napi-modules", + "--abort-on-uncaught-exception", + "--diagnostic-dir", + "--verify-base-objects", + "--no-verify-base-objects", + "--unhandled-rejections", + "--perf-basic-prof", + "--trace-atomics-wait", + "--no-trace-atomics-wait", + "--deprecation", + "--no-deprecation", + "--perf-basic-prof-only-functions", + "--perf-prof", + "--max-http-header-size", + "--report-on-signal", + "--no-report-on-signal", + "--throw-deprecation", + "--no-throw-deprecation", + "--warnings", + "--no-warnings", + "--force-fips", + "--no-force-fips", + "--pending-deprecation", + "--no-pending-deprecation", + "--input-type", + "--tls-max-v1.3", + "--no-tls-max-v1.3", + "--tls-min-v1.2", + "--no-tls-min-v1.2", + "--inspect", + "--no-inspect", + "--heapsnapshot-signal", + "--trace-warnings", + "--no-trace-warnings", + "--trace-event-categories", + "--experimental-worker", + "--tls-max-v1.2", + "--no-tls-max-v1.2", + "--perf-prof-unwinding-info", + "--preserve-symlinks-main", + "--no-preserve-symlinks-main", + "--policy-integrity", + "--experimental-wasm-modules", + "--no-experimental-wasm-modules", + "--node-memory-debug", + "--inspect-publish-uid", + "--tls-min-v1.3", + "--no-tls-min-v1.3", + "--experimental-specifier-resolution", + "--secure-heap", + "--tls-min-v1.0", + "--no-tls-min-v1.0", + "--redirect-warnings", + "--experimental-report", + "--trace-event-file-pattern", + "--trace-uncaught", + "--no-trace-uncaught", + "--experimental-loader", + "--http-parser", + "--dns-result-order", + "--trace-sigint", + "--no-trace-sigint", + "--secure-heap-min", + "--enable-fips", + "--no-enable-fips", + "--enable-source-maps", + "--no-enable-source-maps", + "--insecure-http-parser", + "--no-insecure-http-parser", + "--use-openssl-ca", + "--no-use-openssl-ca", + "--tls-cipher-list", + "--experimental-top-level-await", + "--no-experimental-top-level-await", + "--openssl-config", + "--icu-data-dir", + "--v8-pool-size", + "--report-on-fatalerror", + "--no-report-on-fatalerror", + "--title", + "--tls-min-v1.1", + "--no-tls-min-v1.1", + "--report-filename", + "--trace-deprecation", + "--no-trace-deprecation", + "--report-compact", + "--no-report-compact", + "--experimental-policy", + "--experimental-import-meta-resolve", + "--no-experimental-import-meta-resolve", + "--zero-fill-buffers", + "--no-zero-fill-buffers", + "--report-dir", + "--use-bundled-ca", + "--no-use-bundled-ca", + "--experimental-vm-modules", + "--no-experimental-vm-modules", + "--force-async-hooks-checks", + "--no-force-async-hooks-checks", + "--frozen-intrinsics", + "--no-frozen-intrinsics", + "--huge-max-old-generation-size", + "--disable-proto", + "--debug-arraybuffer-allocations", + "--no-debug-arraybuffer-allocations", + "--conditions", + "--experimental-wasi-unstable-preview1", + "--no-experimental-wasi-unstable-preview1", + "--trace-sync-io", + "--no-trace-sync-io", + "--use-largepages", + "--experimental-abortcontroller", + "--debug-port", + "--es-module-specifier-resolution", + "--prof-process", + "-C", + "--loader", + "--report-directory", + "-r", + "--trace-events-enabled", + ]; + + /* + function isAccepted(to) { + if (!to.startsWith("-") || to === "--") return true; + const recursiveExpansion = aliases.get(to); + if (recursiveExpansion) { + if (recursiveExpansion[0] === to) { + recursiveExpansion.splice(0, 1); + } + return recursiveExpansion.every(isAccepted); + } + return options.get(to).envVarSettings === kAllowedInEnvironment; + } + for (const { 0: from, 1: expansion } of aliases) { + if (expansion.every(isAccepted)) { + let canonical = from; + if (canonical.endsWith("=")) { + canonical = canonical.slice(0, canonical.length - 1); + } + if (canonical.endsWith(" <arg>")) { + canonical = canonical.slice(0, canonical.length - 4); + } + allowedNodeEnvironmentFlags.push(canonical); + } + } + */ + + const trimLeadingDashes = (flag) => flag.replace(leadingDashesRegex, ""); + + // Save these for comparison against flags provided to + // process.allowedNodeEnvironmentFlags.has() which lack leading dashes. + const nodeFlags = allowedNodeEnvironmentFlags.map(trimLeadingDashes); + + class NodeEnvironmentFlagsSet extends Set { + constructor(array) { + super(); + this[kInternal] = { array }; + } + + add() { + // No-op, `Set` API compatible + return this; + } + + delete() { + // No-op, `Set` API compatible + return false; + } + + clear() { + // No-op, `Set` API compatible + } + + has(key) { + // This will return `true` based on various possible + // permutations of a flag, including present/missing leading + // dash(es) and/or underscores-for-dashes. + // Strips any values after `=`, inclusive. + // TODO(addaleax): It might be more flexible to run the option parser + // on a dummy option set and see whether it rejects the argument or + // not. + if (typeof key === "string") { + key = key.replace(replaceUnderscoresRegex, "-"); + if (leadingDashesRegex.test(key)) { + key = key.replace(trailingValuesRegex, ""); + return this[kInternal].array.includes(key); + } + return nodeFlags.includes(key); + } + return false; + } + + entries() { + this[kInternal].set ??= new Set(this[kInternal].array); + return this[kInternal].set.entries(); + } + + forEach(callback, thisArg = undefined) { + this[kInternal].array.forEach((v) => + Reflect.apply(callback, thisArg, [v, v, this]) + ); + } + + get size() { + return this[kInternal].array.length; + } + + values() { + this[kInternal].set ??= new Set(this[kInternal].array); + return this[kInternal].set.values(); + } + } + NodeEnvironmentFlagsSet.prototype.keys = + NodeEnvironmentFlagsSet + .prototype[Symbol.iterator] = + NodeEnvironmentFlagsSet.prototype.values; + + Object.freeze(NodeEnvironmentFlagsSet.prototype.constructor); + Object.freeze(NodeEnvironmentFlagsSet.prototype); + + return Object.freeze( + new NodeEnvironmentFlagsSet( + allowedNodeEnvironmentFlags, + ), + ); +} diff --git a/ext/node/polyfills/internal/querystring.ts b/ext/node/polyfills/internal/querystring.ts new file mode 100644 index 000000000..c00803afe --- /dev/null +++ b/ext/node/polyfills/internal/querystring.ts @@ -0,0 +1,92 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { ERR_INVALID_URI } from "internal:deno_node/polyfills/internal/errors.ts"; + +export const hexTable = new Array(256); +for (let i = 0; i < 256; ++i) { + hexTable[i] = "%" + ((i < 16 ? "0" : "") + i.toString(16)).toUpperCase(); +} + +// deno-fmt-ignore +export const isHexTable = new Int8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 32 - 47 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63 + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 64 - 79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80 - 95 + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 96 - 111 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 112 - 127 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128 ... + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ... 256 +]); + +export function encodeStr( + str: string, + noEscapeTable: Int8Array, + hexTable: string[], +): string { + const len = str.length; + if (len === 0) return ""; + + let out = ""; + let lastPos = 0; + + for (let i = 0; i < len; i++) { + let c = str.charCodeAt(i); + // ASCII + if (c < 0x80) { + if (noEscapeTable[c] === 1) continue; + if (lastPos < i) out += str.slice(lastPos, i); + lastPos = i + 1; + out += hexTable[c]; + continue; + } + + if (lastPos < i) out += str.slice(lastPos, i); + + // Multi-byte characters ... + if (c < 0x800) { + lastPos = i + 1; + out += hexTable[0xc0 | (c >> 6)] + hexTable[0x80 | (c & 0x3f)]; + continue; + } + if (c < 0xd800 || c >= 0xe000) { + lastPos = i + 1; + out += hexTable[0xe0 | (c >> 12)] + + hexTable[0x80 | ((c >> 6) & 0x3f)] + + hexTable[0x80 | (c & 0x3f)]; + continue; + } + // Surrogate pair + ++i; + + // This branch should never happen because all URLSearchParams entries + // should already be converted to USVString. But, included for + // completion's sake anyway. + if (i >= len) throw new ERR_INVALID_URI(); + + const c2 = str.charCodeAt(i) & 0x3ff; + + lastPos = i + 1; + c = 0x10000 + (((c & 0x3ff) << 10) | c2); + out += hexTable[0xf0 | (c >> 18)] + + hexTable[0x80 | ((c >> 12) & 0x3f)] + + hexTable[0x80 | ((c >> 6) & 0x3f)] + + hexTable[0x80 | (c & 0x3f)]; + } + if (lastPos === 0) return str; + if (lastPos < len) return out + str.slice(lastPos); + return out; +} + +export default { + hexTable, + encodeStr, + isHexTable, +}; diff --git a/ext/node/polyfills/internal/readline/callbacks.mjs b/ext/node/polyfills/internal/readline/callbacks.mjs new file mode 100644 index 000000000..3be88c899 --- /dev/null +++ b/ext/node/polyfills/internal/readline/callbacks.mjs @@ -0,0 +1,137 @@ +// Copyright 2018-2023 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. + +"use strict"; + +import { ERR_INVALID_ARG_VALUE, ERR_INVALID_CURSOR_POS } from "internal:deno_node/polyfills/internal/errors.ts"; + +import { validateFunction } from "internal:deno_node/polyfills/internal/validators.mjs"; + +import { CSI } from "internal:deno_node/polyfills/internal/readline/utils.mjs"; + +const { + kClearLine, + kClearScreenDown, + kClearToLineBeginning, + kClearToLineEnd, +} = CSI; + +/** + * moves the cursor to the x and y coordinate on the given stream + */ + +export function cursorTo(stream, x, y, callback) { + if (callback !== undefined) { + validateFunction(callback, "callback"); + } + + if (typeof y === "function") { + callback = y; + y = undefined; + } + + if (Number.isNaN(x)) throw new ERR_INVALID_ARG_VALUE("x", x); + if (Number.isNaN(y)) throw new ERR_INVALID_ARG_VALUE("y", y); + + if (stream == null || (typeof x !== "number" && typeof y !== "number")) { + if (typeof callback === "function") process.nextTick(callback, null); + return true; + } + + if (typeof x !== "number") throw new ERR_INVALID_CURSOR_POS(); + + const data = typeof y !== "number" ? CSI`${x + 1}G` : CSI`${y + 1};${x + 1}H`; + return stream.write(data, callback); +} + +/** + * moves the cursor relative to its current location + */ + +export function moveCursor(stream, dx, dy, callback) { + if (callback !== undefined) { + validateFunction(callback, "callback"); + } + + if (stream == null || !(dx || dy)) { + if (typeof callback === "function") process.nextTick(callback, null); + return true; + } + + let data = ""; + + if (dx < 0) { + data += CSI`${-dx}D`; + } else if (dx > 0) { + data += CSI`${dx}C`; + } + + if (dy < 0) { + data += CSI`${-dy}A`; + } else if (dy > 0) { + data += CSI`${dy}B`; + } + + return stream.write(data, callback); +} + +/** + * clears the current line the cursor is on: + * -1 for left of the cursor + * +1 for right of the cursor + * 0 for the entire line + */ + +export function clearLine(stream, dir, callback) { + if (callback !== undefined) { + validateFunction(callback, "callback"); + } + + if (stream === null || stream === undefined) { + if (typeof callback === "function") process.nextTick(callback, null); + return true; + } + + const type = dir < 0 + ? kClearToLineBeginning + : dir > 0 + ? kClearToLineEnd + : kClearLine; + return stream.write(type, callback); +} + +/** + * clears the screen from the current position of the cursor down + */ + +export function clearScreenDown(stream, callback) { + if (callback !== undefined) { + validateFunction(callback, "callback"); + } + + if (stream === null || stream === undefined) { + if (typeof callback === "function") process.nextTick(callback, null); + return true; + } + + return stream.write(kClearScreenDown, callback); +} diff --git a/ext/node/polyfills/internal/readline/emitKeypressEvents.mjs b/ext/node/polyfills/internal/readline/emitKeypressEvents.mjs new file mode 100644 index 000000000..7f68dac47 --- /dev/null +++ b/ext/node/polyfills/internal/readline/emitKeypressEvents.mjs @@ -0,0 +1,106 @@ +// Copyright 2018-2023 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 { charLengthAt, CSI, emitKeys } from "internal:deno_node/polyfills/internal/readline/utils.mjs"; +import { kSawKeyPress } from "internal:deno_node/polyfills/internal/readline/symbols.mjs"; +import { clearTimeout, setTimeout } from "internal:deno_node/polyfills/timers.ts"; + +const { + kEscape, +} = CSI; + +import { StringDecoder } from "internal:deno_node/polyfills/string_decoder.ts"; + +const KEYPRESS_DECODER = Symbol("keypress-decoder"); +const ESCAPE_DECODER = Symbol("escape-decoder"); + +// GNU readline library - keyseq-timeout is 500ms (default) +const ESCAPE_CODE_TIMEOUT = 500; + +/** + * accepts a readable Stream instance and makes it emit "keypress" events + */ + +export function emitKeypressEvents(stream, iface = {}) { + if (stream[KEYPRESS_DECODER]) return; + + stream[KEYPRESS_DECODER] = new StringDecoder("utf8"); + + stream[ESCAPE_DECODER] = emitKeys(stream); + stream[ESCAPE_DECODER].next(); + + const triggerEscape = () => stream[ESCAPE_DECODER].next(""); + const { escapeCodeTimeout = ESCAPE_CODE_TIMEOUT } = iface; + let timeoutId; + + function onData(input) { + if (stream.listenerCount("keypress") > 0) { + const string = stream[KEYPRESS_DECODER].write(input); + if (string) { + clearTimeout(timeoutId); + + // This supports characters of length 2. + iface[kSawKeyPress] = charLengthAt(string, 0) === string.length; + iface.isCompletionEnabled = false; + + let length = 0; + for (const character of string[Symbol.iterator]()) { + length += character.length; + if (length === string.length) { + iface.isCompletionEnabled = true; + } + + try { + stream[ESCAPE_DECODER].next(character); + // Escape letter at the tail position + if (length === string.length && character === kEscape) { + timeoutId = setTimeout(triggerEscape, escapeCodeTimeout); + } + } catch (err) { + // If the generator throws (it could happen in the `keypress` + // event), we need to restart it. + stream[ESCAPE_DECODER] = emitKeys(stream); + stream[ESCAPE_DECODER].next(); + throw err; + } + } + } + } else { + // Nobody's watching anyway + stream.removeListener("data", onData); + stream.on("newListener", onNewListener); + } + } + + function onNewListener(event) { + if (event === "keypress") { + stream.on("data", onData); + stream.removeListener("newListener", onNewListener); + } + } + + if (stream.listenerCount("keypress") > 0) { + stream.on("data", onData); + } else { + stream.on("newListener", onNewListener); + } +} diff --git a/ext/node/polyfills/internal/readline/interface.mjs b/ext/node/polyfills/internal/readline/interface.mjs new file mode 100644 index 000000000..41d05fbf2 --- /dev/null +++ b/ext/node/polyfills/internal/readline/interface.mjs @@ -0,0 +1,1223 @@ +// Copyright 2018-2023 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. + +// deno-lint-ignore-file camelcase no-inner-declarations no-this-alias + +import { ERR_INVALID_ARG_VALUE, ERR_USE_AFTER_CLOSE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { + validateAbortSignal, + validateArray, + validateString, + validateUint32, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { + // inspect, + getStringWidth, + stripVTControlCharacters, +} from "internal:deno_node/polyfills/internal/util/inspect.mjs"; +import EventEmitter from "internal:deno_node/polyfills/events.ts"; +import { emitKeypressEvents } from "internal:deno_node/polyfills/internal/readline/emitKeypressEvents.mjs"; +import { + charLengthAt, + charLengthLeft, + commonPrefix, + kSubstringSearch, +} from "internal:deno_node/polyfills/internal/readline/utils.mjs"; +import { clearScreenDown, cursorTo, moveCursor } from "internal:deno_node/polyfills/internal/readline/callbacks.mjs"; +import { Readable } from "internal:deno_node/polyfills/_stream.mjs"; + +import { StringDecoder } from "internal:deno_node/polyfills/string_decoder.ts"; +import { + kAddHistory, + kDecoder, + kDeleteLeft, + kDeleteLineLeft, + kDeleteLineRight, + kDeleteRight, + kDeleteWordLeft, + kDeleteWordRight, + kGetDisplayPos, + kHistoryNext, + kHistoryPrev, + kInsertString, + kLine, + kLine_buffer, + kMoveCursor, + kNormalWrite, + kOldPrompt, + kOnLine, + kPreviousKey, + kPrompt, + kQuestionCallback, + kRefreshLine, + kSawKeyPress, + kSawReturnAt, + kSetRawMode, + kTabComplete, + kTabCompleter, + kTtyWrite, + kWordLeft, + kWordRight, + kWriteToOutput, +} from "internal:deno_node/polyfills/internal/readline/symbols.mjs"; + +const kHistorySize = 30; +const kMincrlfDelay = 100; +// \r\n, \n, or \r followed by something other than \n +const lineEnding = /\r?\n|\r(?!\n)/; + +const kLineObjectStream = Symbol("line object stream"); +export const kQuestionCancel = Symbol("kQuestionCancel"); +export const kQuestion = Symbol("kQuestion"); + +// GNU readline library - keyseq-timeout is 500ms (default) +const ESCAPE_CODE_TIMEOUT = 500; + +export { + kAddHistory, + kDecoder, + kDeleteLeft, + kDeleteLineLeft, + kDeleteLineRight, + kDeleteRight, + kDeleteWordLeft, + kDeleteWordRight, + kGetDisplayPos, + kHistoryNext, + kHistoryPrev, + kInsertString, + kLine, + kLine_buffer, + kMoveCursor, + kNormalWrite, + kOldPrompt, + kOnLine, + kPreviousKey, + kPrompt, + kQuestionCallback, + kRefreshLine, + kSawKeyPress, + kSawReturnAt, + kSetRawMode, + kTabComplete, + kTabCompleter, + kTtyWrite, + kWordLeft, + kWordRight, + kWriteToOutput, +}; + +export function InterfaceConstructor(input, output, completer, terminal) { + this[kSawReturnAt] = 0; + // TODO(BridgeAR): Document this property. The name is not ideal, so we + // might want to expose an alias and document that instead. + this.isCompletionEnabled = true; + this[kSawKeyPress] = false; + this[kPreviousKey] = null; + this.escapeCodeTimeout = ESCAPE_CODE_TIMEOUT; + this.tabSize = 8; + Function.prototype.call(EventEmitter, this); + + let history; + let historySize; + let removeHistoryDuplicates = false; + let crlfDelay; + let prompt = "> "; + let signal; + + if (input?.input) { + // An options object was given + output = input.output; + completer = input.completer; + terminal = input.terminal; + history = input.history; + historySize = input.historySize; + signal = input.signal; + if (input.tabSize !== undefined) { + validateUint32(input.tabSize, "tabSize", true); + this.tabSize = input.tabSize; + } + removeHistoryDuplicates = input.removeHistoryDuplicates; + if (input.prompt !== undefined) { + prompt = input.prompt; + } + if (input.escapeCodeTimeout !== undefined) { + if (Number.isFinite(input.escapeCodeTimeout)) { + this.escapeCodeTimeout = input.escapeCodeTimeout; + } else { + throw new ERR_INVALID_ARG_VALUE( + "input.escapeCodeTimeout", + this.escapeCodeTimeout, + ); + } + } + + if (signal) { + validateAbortSignal(signal, "options.signal"); + } + + crlfDelay = input.crlfDelay; + input = input.input; + } + + if (completer !== undefined && typeof completer !== "function") { + throw new ERR_INVALID_ARG_VALUE("completer", completer); + } + + if (history === undefined) { + history = []; + } else { + validateArray(history, "history"); + } + + if (historySize === undefined) { + historySize = kHistorySize; + } + + if ( + typeof historySize !== "number" || + Number.isNaN(historySize) || + historySize < 0 + ) { + throw new ERR_INVALID_ARG_VALUE.RangeError("historySize", historySize); + } + + // Backwards compat; check the isTTY prop of the output stream + // when `terminal` was not specified + if (terminal === undefined && !(output === null || output === undefined)) { + terminal = !!output.isTTY; + } + + const self = this; + + this.line = ""; + this[kSubstringSearch] = null; + this.output = output; + this.input = input; + this.history = history; + this.historySize = historySize; + this.removeHistoryDuplicates = !!removeHistoryDuplicates; + this.crlfDelay = crlfDelay + ? Math.max(kMincrlfDelay, crlfDelay) + : kMincrlfDelay; + this.completer = completer; + + this.setPrompt(prompt); + + this.terminal = !!terminal; + + function onerror(err) { + self.emit("error", err); + } + + function ondata(data) { + self[kNormalWrite](data); + } + + function onend() { + if ( + typeof self[kLine_buffer] === "string" && + self[kLine_buffer].length > 0 + ) { + self.emit("line", self[kLine_buffer]); + } + self.close(); + } + + function ontermend() { + if (typeof self.line === "string" && self.line.length > 0) { + self.emit("line", self.line); + } + self.close(); + } + + function onkeypress(s, key) { + self[kTtyWrite](s, key); + if (key && key.sequence) { + // If the key.sequence is half of a surrogate pair + // (>= 0xd800 and <= 0xdfff), refresh the line so + // the character is displayed appropriately. + const ch = key.sequence.codePointAt(0); + if (ch >= 0xd800 && ch <= 0xdfff) self[kRefreshLine](); + } + } + + function onresize() { + self[kRefreshLine](); + } + + this[kLineObjectStream] = undefined; + + input.on("error", onerror); + + if (!this.terminal) { + function onSelfCloseWithoutTerminal() { + input.removeListener("data", ondata); + input.removeListener("error", onerror); + input.removeListener("end", onend); + } + + input.on("data", ondata); + input.on("end", onend); + self.once("close", onSelfCloseWithoutTerminal); + this[kDecoder] = new StringDecoder("utf8"); + } else { + function onSelfCloseWithTerminal() { + input.removeListener("keypress", onkeypress); + input.removeListener("error", onerror); + input.removeListener("end", ontermend); + if (output !== null && output !== undefined) { + output.removeListener("resize", onresize); + } + } + + emitKeypressEvents(input, this); + + // `input` usually refers to stdin + input.on("keypress", onkeypress); + input.on("end", ontermend); + + this[kSetRawMode](true); + this.terminal = true; + + // Cursor position on the line. + this.cursor = 0; + + this.historyIndex = -1; + + if (output !== null && output !== undefined) { + output.on("resize", onresize); + } + + self.once("close", onSelfCloseWithTerminal); + } + + if (signal) { + const onAborted = () => self.close(); + if (signal.aborted) { + process.nextTick(onAborted); + } else { + signal.addEventListener("abort", onAborted, { once: true }); + self.once("close", () => signal.removeEventListener("abort", onAborted)); + } + } + + // Current line + this.line = ""; + + input.resume(); +} + +Object.setPrototypeOf(InterfaceConstructor.prototype, EventEmitter.prototype); +Object.setPrototypeOf(InterfaceConstructor, EventEmitter); + +export class Interface extends InterfaceConstructor { + // eslint-disable-next-line no-useless-constructor + constructor(input, output, completer, terminal) { + super(input, output, completer, terminal); + } + get columns() { + if (this.output && this.output.columns) return this.output.columns; + return Infinity; + } + + /** + * Sets the prompt written to the output. + * @param {string} prompt + * @returns {void} + */ + setPrompt(prompt) { + this[kPrompt] = prompt; + } + + /** + * Returns the current prompt used by `rl.prompt()`. + * @returns {string} + */ + getPrompt() { + return this[kPrompt]; + } + + [kSetRawMode](mode) { + const wasInRawMode = this.input.isRaw; + + if (typeof this.input.setRawMode === "function") { + this.input.setRawMode(mode); + } + + return wasInRawMode; + } + + /** + * Writes the configured `prompt` to a new line in `output`. + * @param {boolean} [preserveCursor] + * @returns {void} + */ + prompt(preserveCursor) { + if (this.paused) this.resume(); + if (this.terminal && process.env.TERM !== "dumb") { + if (!preserveCursor) this.cursor = 0; + this[kRefreshLine](); + } else { + this[kWriteToOutput](this[kPrompt]); + } + } + + [kQuestion](query, cb) { + if (this.closed) { + throw new ERR_USE_AFTER_CLOSE("readline"); + } + if (this[kQuestionCallback]) { + this.prompt(); + } else { + this[kOldPrompt] = this[kPrompt]; + this.setPrompt(query); + this[kQuestionCallback] = cb; + this.prompt(); + } + } + + [kOnLine](line) { + if (this[kQuestionCallback]) { + const cb = this[kQuestionCallback]; + this[kQuestionCallback] = null; + this.setPrompt(this[kOldPrompt]); + cb(line); + } else { + this.emit("line", line); + } + } + + [kQuestionCancel]() { + if (this[kQuestionCallback]) { + this[kQuestionCallback] = null; + this.setPrompt(this[kOldPrompt]); + this.clearLine(); + } + } + + [kWriteToOutput](stringToWrite) { + validateString(stringToWrite, "stringToWrite"); + + if (this.output !== null && this.output !== undefined) { + this.output.write(stringToWrite); + } + } + + [kAddHistory]() { + if (this.line.length === 0) return ""; + + // If the history is disabled then return the line + if (this.historySize === 0) return this.line; + + // If the trimmed line is empty then return the line + if (this.line.trim().length === 0) return this.line; + + if (this.history.length === 0 || this.history[0] !== this.line) { + if (this.removeHistoryDuplicates) { + // Remove older history line if identical to new one + const dupIndex = this.history.indexOf(this.line); + if (dupIndex !== -1) this.history.splice(dupIndex, 1); + } + + this.history.unshift(this.line); + + // Only store so many + if (this.history.length > this.historySize) { + this.history.pop(); + } + } + + this.historyIndex = -1; + + // The listener could change the history object, possibly + // to remove the last added entry if it is sensitive and should + // not be persisted in the history, like a password + const line = this.history[0]; + + // Emit history event to notify listeners of update + this.emit("history", this.history); + + return line; + } + + [kRefreshLine]() { + // line length + const line = this[kPrompt] + this.line; + const dispPos = this[kGetDisplayPos](line); + const lineCols = dispPos.cols; + const lineRows = dispPos.rows; + + // cursor position + const cursorPos = this.getCursorPos(); + + // First move to the bottom of the current line, based on cursor pos + const prevRows = this.prevRows || 0; + if (prevRows > 0) { + moveCursor(this.output, 0, -prevRows); + } + + // Cursor to left edge. + cursorTo(this.output, 0); + // erase data + clearScreenDown(this.output); + + // Write the prompt and the current buffer content. + this[kWriteToOutput](line); + + // Force terminal to allocate a new line + if (lineCols === 0) { + this[kWriteToOutput](" "); + } + + // Move cursor to original position. + cursorTo(this.output, cursorPos.cols); + + const diff = lineRows - cursorPos.rows; + if (diff > 0) { + moveCursor(this.output, 0, -diff); + } + + this.prevRows = cursorPos.rows; + } + + /** + * Closes the `readline.Interface` instance. + * @returns {void} + */ + close() { + if (this.closed) return; + this.pause(); + if (this.terminal) { + this[kSetRawMode](false); + } + this.closed = true; + this.emit("close"); + } + + /** + * Pauses the `input` stream. + * @returns {void | Interface} + */ + pause() { + if (this.paused) return; + this.input.pause(); + this.paused = true; + this.emit("pause"); + return this; + } + + /** + * Resumes the `input` stream if paused. + * @returns {void | Interface} + */ + resume() { + if (!this.paused) return; + this.input.resume(); + this.paused = false; + this.emit("resume"); + return this; + } + + /** + * Writes either `data` or a `key` sequence identified by + * `key` to the `output`. + * @param {string} d + * @param {{ + * ctrl?: boolean; + * meta?: boolean; + * shift?: boolean; + * name?: string; + * }} [key] + * @returns {void} + */ + write(d, key) { + if (this.paused) this.resume(); + if (this.terminal) { + this[kTtyWrite](d, key); + } else { + this[kNormalWrite](d); + } + } + + [kNormalWrite](b) { + if (b === undefined) { + return; + } + let string = this[kDecoder].write(b); + if ( + this[kSawReturnAt] && + Date.now() - this[kSawReturnAt] <= this.crlfDelay + ) { + string = string.replace(/^\n/, ""); + this[kSawReturnAt] = 0; + } + + // Run test() on the new string chunk, not on the entire line buffer. + const newPartContainsEnding = lineEnding.test(string); + + if (this[kLine_buffer]) { + string = this[kLine_buffer] + string; + this[kLine_buffer] = null; + } + if (newPartContainsEnding) { + this[kSawReturnAt] = string.endsWith("\r") ? Date.now() : 0; + + // Got one or more newlines; process into "line" events + const lines = string.split(lineEnding); + // Either '' or (conceivably) the unfinished portion of the next line + string = lines.pop(); + this[kLine_buffer] = string; + for (let n = 0; n < lines.length; n++) this[kOnLine](lines[n]); + } else if (string) { + // No newlines this time, save what we have for next time + this[kLine_buffer] = string; + } + } + + [kInsertString](c) { + if (this.cursor < this.line.length) { + const beg = this.line.slice(0, this.cursor); + const end = this.line.slice( + this.cursor, + this.line.length, + ); + this.line = beg + c + end; + this.cursor += c.length; + this[kRefreshLine](); + } else { + this.line += c; + this.cursor += c.length; + + if (this.getCursorPos().cols === 0) { + this[kRefreshLine](); + } else { + this[kWriteToOutput](c); + } + } + } + + async [kTabComplete](lastKeypressWasTab) { + this.pause(); + const string = this.line.slice(0, this.cursor); + let value; + try { + value = await this.completer(string); + } catch (err) { + // TODO(bartlomieju): inspect is not ported yet + // this[kWriteToOutput](`Tab completion error: ${inspect(err)}`); + this[kWriteToOutput](`Tab completion error: ${err}`); + return; + } finally { + this.resume(); + } + this[kTabCompleter](lastKeypressWasTab, value); + } + + [kTabCompleter](lastKeypressWasTab, { 0: completions, 1: completeOn }) { + // Result and the text that was completed. + + if (!completions || completions.length === 0) { + return; + } + + // If there is a common prefix to all matches, then apply that portion. + const prefix = commonPrefix( + completions.filter((e) => e !== ""), + ); + if ( + prefix.startsWith(completeOn) && + prefix.length > completeOn.length + ) { + this[kInsertString](prefix.slice(completeOn.length)); + return; + } else if (!completeOn.startsWith(prefix)) { + this.line = this.line.slice(0, this.cursor - completeOn.length) + + prefix + + this.line.slice(this.cursor, this.line.length); + this.cursor = this.cursor - completeOn.length + prefix.length; + this._refreshLine(); + return; + } + + if (!lastKeypressWasTab) { + return; + } + + // Apply/show completions. + const completionsWidth = completions.map( + (e) => getStringWidth(e), + ); + const width = Math.max.apply(completionsWidth) + 2; // 2 space padding + let maxColumns = Math.floor(this.columns / width) || 1; + if (maxColumns === Infinity) { + maxColumns = 1; + } + let output = "\r\n"; + let lineIndex = 0; + let whitespace = 0; + for (let i = 0; i < completions.length; i++) { + const completion = completions[i]; + if (completion === "" || lineIndex === maxColumns) { + output += "\r\n"; + lineIndex = 0; + whitespace = 0; + } else { + output += " ".repeat(whitespace); + } + if (completion !== "") { + output += completion; + whitespace = width - completionsWidth[i]; + lineIndex++; + } else { + output += "\r\n"; + } + } + if (lineIndex !== 0) { + output += "\r\n\r\n"; + } + this[kWriteToOutput](output); + this[kRefreshLine](); + } + + [kWordLeft]() { + if (this.cursor > 0) { + // Reverse the string and match a word near beginning + // to avoid quadratic time complexity + const leading = this.line.slice(0, this.cursor); + const reversed = Array.from(leading).reverse().join(""); + const match = reversed.match(/^\s*(?:[^\w\s]+|\w+)?/); + this[kMoveCursor](-match[0].length); + } + } + + [kWordRight]() { + if (this.cursor < this.line.length) { + const trailing = this.line.slice(this.cursor); + const match = trailing.match(/^(?:\s+|[^\w\s]+|\w+)\s*/); + this[kMoveCursor](match[0].length); + } + } + + [kDeleteLeft]() { + if (this.cursor > 0 && this.line.length > 0) { + // The number of UTF-16 units comprising the character to the left + const charSize = charLengthLeft(this.line, this.cursor); + this.line = this.line.slice(0, this.cursor - charSize) + + this.line.slice(this.cursor, this.line.length); + + this.cursor -= charSize; + this[kRefreshLine](); + } + } + + [kDeleteRight]() { + if (this.cursor < this.line.length) { + // The number of UTF-16 units comprising the character to the left + const charSize = charLengthAt(this.line, this.cursor); + this.line = this.line.slice(0, this.cursor) + + this.line.slice( + this.cursor + charSize, + this.line.length, + ); + this[kRefreshLine](); + } + } + + [kDeleteWordLeft]() { + if (this.cursor > 0) { + // Reverse the string and match a word near beginning + // to avoid quadratic time complexity + let leading = this.line.slice(0, this.cursor); + const reversed = Array.from(leading).reverse().join(""); + const match = reversed.match(/^\s*(?:[^\w\s]+|\w+)?/); + leading = leading.slice( + 0, + leading.length - match[0].length, + ); + this.line = leading + + this.line.slice(this.cursor, this.line.length); + this.cursor = leading.length; + this[kRefreshLine](); + } + } + + [kDeleteWordRight]() { + if (this.cursor < this.line.length) { + const trailing = this.line.slice(this.cursor); + const match = trailing.match(/^(?:\s+|\W+|\w+)\s*/); + this.line = this.line.slice(0, this.cursor) + + trailing.slice(match[0].length); + this[kRefreshLine](); + } + } + + [kDeleteLineLeft]() { + this.line = this.line.slice(this.cursor); + this.cursor = 0; + this[kRefreshLine](); + } + + [kDeleteLineRight]() { + this.line = this.line.slice(0, this.cursor); + this[kRefreshLine](); + } + + clearLine() { + this[kMoveCursor](+Infinity); + this[kWriteToOutput]("\r\n"); + this.line = ""; + this.cursor = 0; + this.prevRows = 0; + } + + [kLine]() { + const line = this[kAddHistory](); + this.clearLine(); + this[kOnLine](line); + } + + // TODO(BridgeAR): Add underscores to the search part and a red background in + // case no match is found. This should only be the visual part and not the + // actual line content! + // TODO(BridgeAR): In case the substring based search is active and the end is + // reached, show a comment how to search the history as before. E.g., using + // <ctrl> + N. Only show this after two/three UPs or DOWNs, not on the first + // one. + [kHistoryNext]() { + if (this.historyIndex >= 0) { + const search = this[kSubstringSearch] || ""; + let index = this.historyIndex - 1; + while ( + index >= 0 && + (!this.history[index].startsWith(search) || + this.line === this.history[index]) + ) { + index--; + } + if (index === -1) { + this.line = search; + } else { + this.line = this.history[index]; + } + this.historyIndex = index; + this.cursor = this.line.length; // Set cursor to end of line. + this[kRefreshLine](); + } + } + + [kHistoryPrev]() { + if (this.historyIndex < this.history.length && this.history.length) { + const search = this[kSubstringSearch] || ""; + let index = this.historyIndex + 1; + while ( + index < this.history.length && + (!this.history[index].startsWith(search) || + this.line === this.history[index]) + ) { + index++; + } + if (index === this.history.length) { + this.line = search; + } else { + this.line = this.history[index]; + } + this.historyIndex = index; + this.cursor = this.line.length; // Set cursor to end of line. + this[kRefreshLine](); + } + } + + // Returns the last character's display position of the given string + [kGetDisplayPos](str) { + let offset = 0; + const col = this.columns; + let rows = 0; + str = stripVTControlCharacters(str); + for (const char of str[Symbol.iterator]()) { + if (char === "\n") { + // Rows must be incremented by 1 even if offset = 0 or col = +Infinity. + rows += Math.ceil(offset / col) || 1; + offset = 0; + continue; + } + // Tabs must be aligned by an offset of the tab size. + if (char === "\t") { + offset += this.tabSize - (offset % this.tabSize); + continue; + } + const width = getStringWidth(char); + if (width === 0 || width === 1) { + offset += width; + } else { + // width === 2 + if ((offset + 1) % col === 0) { + offset++; + } + offset += 2; + } + } + const cols = offset % col; + rows += (offset - cols) / col; + return { cols, rows }; + } + + /** + * Returns the real position of the cursor in relation + * to the input prompt + string. + * @returns {{ + * rows: number; + * cols: number; + * }} + */ + getCursorPos() { + const strBeforeCursor = this[kPrompt] + + this.line.slice(0, this.cursor); + return this[kGetDisplayPos](strBeforeCursor); + } + + // This function moves cursor dx places to the right + // (-dx for left) and refreshes the line if it is needed. + [kMoveCursor](dx) { + if (dx === 0) { + return; + } + const oldPos = this.getCursorPos(); + this.cursor += dx; + + // Bounds check + if (this.cursor < 0) { + this.cursor = 0; + } else if (this.cursor > this.line.length) { + this.cursor = this.line.length; + } + + const newPos = this.getCursorPos(); + + // Check if cursor stayed on the line. + if (oldPos.rows === newPos.rows) { + const diffWidth = newPos.cols - oldPos.cols; + moveCursor(this.output, diffWidth, 0); + } else { + this[kRefreshLine](); + } + } + + // Handle a write from the tty + [kTtyWrite](s, key) { + const previousKey = this[kPreviousKey]; + key = key || {}; + this[kPreviousKey] = key; + + // Activate or deactivate substring search. + if ( + (key.name === "up" || key.name === "down") && + !key.ctrl && + !key.meta && + !key.shift + ) { + if (this[kSubstringSearch] === null) { + this[kSubstringSearch] = this.line.slice( + 0, + this.cursor, + ); + } + } else if (this[kSubstringSearch] !== null) { + this[kSubstringSearch] = null; + // Reset the index in case there's no match. + if (this.history.length === this.historyIndex) { + this.historyIndex = -1; + } + } + + // Ignore escape key, fixes + // https://github.com/nodejs/node-v0.x-archive/issues/2876. + if (key.name === "escape") return; + + if (key.ctrl && key.shift) { + /* Control and shift pressed */ + switch (key.name) { + // TODO(BridgeAR): The transmitted escape sequence is `\b` and that is + // identical to <ctrl>-h. It should have a unique escape sequence. + case "backspace": + this[kDeleteLineLeft](); + break; + + case "delete": + this[kDeleteLineRight](); + break; + } + } else if (key.ctrl) { + /* Control key pressed */ + + switch (key.name) { + case "c": + if (this.listenerCount("SIGINT") > 0) { + this.emit("SIGINT"); + } else { + // This readline instance is finished + this.close(); + } + break; + + case "h": // delete left + this[kDeleteLeft](); + break; + + case "d": // delete right or EOF + if (this.cursor === 0 && this.line.length === 0) { + // This readline instance is finished + this.close(); + } else if (this.cursor < this.line.length) { + this[kDeleteRight](); + } + break; + + case "u": // Delete from current to start of line + this[kDeleteLineLeft](); + break; + + case "k": // Delete from current to end of line + this[kDeleteLineRight](); + break; + + case "a": // Go to the start of the line + this[kMoveCursor](-Infinity); + break; + + case "e": // Go to the end of the line + this[kMoveCursor](+Infinity); + break; + + case "b": // back one character + this[kMoveCursor](-charLengthLeft(this.line, this.cursor)); + break; + + case "f": // Forward one character + this[kMoveCursor](+charLengthAt(this.line, this.cursor)); + break; + + case "l": // Clear the whole screen + cursorTo(this.output, 0, 0); + clearScreenDown(this.output); + this[kRefreshLine](); + break; + + case "n": // next history item + this[kHistoryNext](); + break; + + case "p": // Previous history item + this[kHistoryPrev](); + break; + + case "z": + if (process.platform === "win32") break; + if (this.listenerCount("SIGTSTP") > 0) { + this.emit("SIGTSTP"); + } else { + process.once("SIGCONT", () => { + // Don't raise events if stream has already been abandoned. + if (!this.paused) { + // Stream must be paused and resumed after SIGCONT to catch + // SIGINT, SIGTSTP, and EOF. + this.pause(); + this.emit("SIGCONT"); + } + // Explicitly re-enable "raw mode" and move the cursor to + // the correct position. + // See https://github.com/joyent/node/issues/3295. + this[kSetRawMode](true); + this[kRefreshLine](); + }); + this[kSetRawMode](false); + process.kill(process.pid, "SIGTSTP"); + } + break; + + case "w": // Delete backwards to a word boundary + // TODO(BridgeAR): The transmitted escape sequence is `\b` and that is + // identical to <ctrl>-h. It should have a unique escape sequence. + // Falls through + case "backspace": + this[kDeleteWordLeft](); + break; + + case "delete": // Delete forward to a word boundary + this[kDeleteWordRight](); + break; + + case "left": + this[kWordLeft](); + break; + + case "right": + this[kWordRight](); + break; + } + } else if (key.meta) { + /* Meta key pressed */ + + switch (key.name) { + case "b": // backward word + this[kWordLeft](); + break; + + case "f": // forward word + this[kWordRight](); + break; + + case "d": // delete forward word + case "delete": + this[kDeleteWordRight](); + break; + + case "backspace": // Delete backwards to a word boundary + this[kDeleteWordLeft](); + break; + } + } else { + /* No modifier keys used */ + + // \r bookkeeping is only relevant if a \n comes right after. + if (this[kSawReturnAt] && key.name !== "enter") this[kSawReturnAt] = 0; + + switch (key.name) { + case "return": // Carriage return, i.e. \r + this[kSawReturnAt] = Date.now(); + this[kLine](); + break; + + case "enter": + // When key interval > crlfDelay + if ( + this[kSawReturnAt] === 0 || + Date.now() - this[kSawReturnAt] > this.crlfDelay + ) { + this[kLine](); + } + this[kSawReturnAt] = 0; + break; + + case "backspace": + this[kDeleteLeft](); + break; + + case "delete": + this[kDeleteRight](); + break; + + case "left": + // Obtain the code point to the left + this[kMoveCursor](-charLengthLeft(this.line, this.cursor)); + break; + + case "right": + this[kMoveCursor](+charLengthAt(this.line, this.cursor)); + break; + + case "home": + this[kMoveCursor](-Infinity); + break; + + case "end": + this[kMoveCursor](+Infinity); + break; + + case "up": + this[kHistoryPrev](); + break; + + case "down": + this[kHistoryNext](); + break; + + case "tab": + // If tab completion enabled, do that... + if ( + typeof this.completer === "function" && + this.isCompletionEnabled + ) { + const lastKeypressWasTab = previousKey && + previousKey.name === "tab"; + this[kTabComplete](lastKeypressWasTab); + break; + } + // falls through + default: + if (typeof s === "string" && s) { + const lines = s.split(/\r\n|\n|\r/); + for (let i = 0, len = lines.length; i < len; i++) { + if (i > 0) { + this[kLine](); + } + this[kInsertString](lines[i]); + } + } + } + } + } + + /** + * Creates an `AsyncIterator` object that iterates through + * each line in the input stream as a string. + * @typedef {{ + * [Symbol.asyncIterator]: () => InterfaceAsyncIterator, + * next: () => Promise<string> + * }} InterfaceAsyncIterator + * @returns {InterfaceAsyncIterator} + */ + [Symbol.asyncIterator]() { + if (this[kLineObjectStream] === undefined) { + const readable = new Readable({ + objectMode: true, + read: () => { + this.resume(); + }, + destroy: (err, cb) => { + this.off("line", lineListener); + this.off("close", closeListener); + this.close(); + cb(err); + }, + }); + const lineListener = (input) => { + if (!readable.push(input)) { + // TODO(rexagod): drain to resume flow + this.pause(); + } + }; + const closeListener = () => { + readable.push(null); + }; + const errorListener = (err) => { + readable.destroy(err); + }; + this.on("error", errorListener); + this.on("line", lineListener); + this.on("close", closeListener); + this[kLineObjectStream] = readable; + } + + return this[kLineObjectStream][Symbol.asyncIterator](); + } +} diff --git a/ext/node/polyfills/internal/readline/promises.mjs b/ext/node/polyfills/internal/readline/promises.mjs new file mode 100644 index 000000000..36aa3de12 --- /dev/null +++ b/ext/node/polyfills/internal/readline/promises.mjs @@ -0,0 +1,139 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. + +import { ArrayPrototypeJoin, ArrayPrototypePush } from "internal:deno_node/polyfills/internal/primordials.mjs"; + +import { CSI } from "internal:deno_node/polyfills/internal/readline/utils.mjs"; +import { validateBoolean, validateInteger } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { isWritable } from "internal:deno_node/polyfills/internal/streams/utils.mjs"; +import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; + +const { + kClearToLineBeginning, + kClearToLineEnd, + kClearLine, + kClearScreenDown, +} = CSI; + +export class Readline { + #autoCommit = false; + #stream; + #todo = []; + + constructor(stream, options = undefined) { + if (!isWritable(stream)) { + throw new ERR_INVALID_ARG_TYPE("stream", "Writable", stream); + } + this.#stream = stream; + if (options?.autoCommit != null) { + validateBoolean(options.autoCommit, "options.autoCommit"); + this.#autoCommit = options.autoCommit; + } + } + + /** + * Moves the cursor to the x and y coordinate on the given stream. + * @param {integer} x + * @param {integer} [y] + * @returns {Readline} this + */ + cursorTo(x, y = undefined) { + validateInteger(x, "x"); + if (y != null) validateInteger(y, "y"); + + const data = y == null ? CSI`${x + 1}G` : CSI`${y + 1};${x + 1}H`; + if (this.#autoCommit) process.nextTick(() => this.#stream.write(data)); + else ArrayPrototypePush(this.#todo, data); + + return this; + } + + /** + * Moves the cursor relative to its current location. + * @param {integer} dx + * @param {integer} dy + * @returns {Readline} this + */ + moveCursor(dx, dy) { + if (dx || dy) { + validateInteger(dx, "dx"); + validateInteger(dy, "dy"); + + let data = ""; + + if (dx < 0) { + data += CSI`${-dx}D`; + } else if (dx > 0) { + data += CSI`${dx}C`; + } + + if (dy < 0) { + data += CSI`${-dy}A`; + } else if (dy > 0) { + data += CSI`${dy}B`; + } + if (this.#autoCommit) process.nextTick(() => this.#stream.write(data)); + else ArrayPrototypePush(this.#todo, data); + } + return this; + } + + /** + * Clears the current line the cursor is on. + * @param {-1|0|1} dir Direction to clear: + * -1 for left of the cursor + * +1 for right of the cursor + * 0 for the entire line + * @returns {Readline} this + */ + clearLine(dir) { + validateInteger(dir, "dir", -1, 1); + + const data = dir < 0 + ? kClearToLineBeginning + : dir > 0 + ? kClearToLineEnd + : kClearLine; + if (this.#autoCommit) process.nextTick(() => this.#stream.write(data)); + else ArrayPrototypePush(this.#todo, data); + return this; + } + + /** + * Clears the screen from the current position of the cursor down. + * @returns {Readline} this + */ + clearScreenDown() { + if (this.#autoCommit) { + process.nextTick(() => this.#stream.write(kClearScreenDown)); + } else { + ArrayPrototypePush(this.#todo, kClearScreenDown); + } + return this; + } + + /** + * Sends all the pending actions to the associated `stream` and clears the + * internal list of pending actions. + * @returns {Promise<void>} Resolves when all pending actions have been + * flushed to the associated `stream`. + */ + commit() { + return new Promise((resolve) => { + this.#stream.write(ArrayPrototypeJoin(this.#todo, ""), resolve); + this.#todo = []; + }); + } + + /** + * Clears the internal list of pending actions without sending it to the + * associated `stream`. + * @returns {Readline} this + */ + rollback() { + this.#todo = []; + return this; + } +} + +export default Readline; diff --git a/ext/node/polyfills/internal/readline/symbols.mjs b/ext/node/polyfills/internal/readline/symbols.mjs new file mode 100644 index 000000000..3a05c64d2 --- /dev/null +++ b/ext/node/polyfills/internal/readline/symbols.mjs @@ -0,0 +1,34 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +export const kAddHistory = Symbol("_addHistory"); +export const kDecoder = Symbol("_decoder"); +export const kDeleteLeft = Symbol("_deleteLeft"); +export const kDeleteLineLeft = Symbol("_deleteLineLeft"); +export const kDeleteLineRight = Symbol("_deleteLineRight"); +export const kDeleteRight = Symbol("_deleteRight"); +export const kDeleteWordLeft = Symbol("_deleteWordLeft"); +export const kDeleteWordRight = Symbol("_deleteWordRight"); +export const kGetDisplayPos = Symbol("_getDisplayPos"); +export const kHistoryNext = Symbol("_historyNext"); +export const kHistoryPrev = Symbol("_historyPrev"); +export const kInsertString = Symbol("_insertString"); +export const kLine = Symbol("_line"); +export const kLine_buffer = Symbol("_line_buffer"); +export const kMoveCursor = Symbol("_moveCursor"); +export const kNormalWrite = Symbol("_normalWrite"); +export const kOldPrompt = Symbol("_oldPrompt"); +export const kOnLine = Symbol("_onLine"); +export const kPreviousKey = Symbol("_previousKey"); +export const kPrompt = Symbol("_prompt"); +export const kQuestionCallback = Symbol("_questionCallback"); +export const kRefreshLine = Symbol("_refreshLine"); +export const kSawKeyPress = Symbol("_sawKeyPress"); +export const kSawReturnAt = Symbol("_sawReturnAt"); +export const kSetRawMode = Symbol("_setRawMode"); +export const kTabComplete = Symbol("_tabComplete"); +export const kTabCompleter = Symbol("_tabCompleter"); +export const kTtyWrite = Symbol("_ttyWrite"); +export const kWordLeft = Symbol("_wordLeft"); +export const kWordRight = Symbol("_wordRight"); +export const kWriteToOutput = Symbol("_writeToOutput"); diff --git a/ext/node/polyfills/internal/readline/utils.mjs b/ext/node/polyfills/internal/readline/utils.mjs new file mode 100644 index 000000000..6224f112b --- /dev/null +++ b/ext/node/polyfills/internal/readline/utils.mjs @@ -0,0 +1,580 @@ +// Copyright 2018-2023 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. + +"use strict"; + +const kUTF16SurrogateThreshold = 0x10000; // 2 ** 16 +const kEscape = "\x1b"; +export const kSubstringSearch = Symbol("kSubstringSearch"); + +export function CSI(strings, ...args) { + let ret = `${kEscape}[`; + for (let n = 0; n < strings.length; n++) { + ret += strings[n]; + if (n < args.length) { + ret += args[n]; + } + } + return ret; +} + +CSI.kEscape = kEscape; +CSI.kClearToLineBeginning = CSI`1K`; +CSI.kClearToLineEnd = CSI`0K`; +CSI.kClearLine = CSI`2K`; +CSI.kClearScreenDown = CSI`0J`; + +// TODO(BridgeAR): Treat combined characters as single character, i.e, +// 'a\u0301' and '\u0301a' (both have the same visual output). +// Check Canonical_Combining_Class in +// http://userguide.icu-project.org/strings/properties +export function charLengthLeft(str, i) { + if (i <= 0) { + return 0; + } + if ( + (i > 1 && + str.codePointAt(i - 2) >= kUTF16SurrogateThreshold) || + str.codePointAt(i - 1) >= kUTF16SurrogateThreshold + ) { + return 2; + } + return 1; +} + +export function charLengthAt(str, i) { + if (str.length <= i) { + // Pretend to move to the right. This is necessary to autocomplete while + // moving to the right. + return 1; + } + return str.codePointAt(i) >= kUTF16SurrogateThreshold ? 2 : 1; +} + +/* + Some patterns seen in terminal key escape codes, derived from combos seen + at http://www.midnight-commander.org/browser/lib/tty/key.c + + ESC letter + ESC [ letter + ESC [ modifier letter + ESC [ 1 ; modifier letter + ESC [ num char + ESC [ num ; modifier char + ESC O letter + ESC O modifier letter + ESC O 1 ; modifier letter + ESC N letter + ESC [ [ num ; modifier char + ESC [ [ 1 ; modifier letter + ESC ESC [ num char + ESC ESC O letter + + - char is usually ~ but $ and ^ also happen with rxvt + - modifier is 1 + + (shift * 1) + + (left_alt * 2) + + (ctrl * 4) + + (right_alt * 8) + - two leading ESCs apparently mean the same as one leading ESC +*/ +export function* emitKeys(stream) { + while (true) { + let ch = yield; + let s = ch; + let escaped = false; + const key = { + sequence: null, + name: undefined, + ctrl: false, + meta: false, + shift: false, + }; + + if (ch === kEscape) { + escaped = true; + s += ch = yield; + + if (ch === kEscape) { + s += ch = yield; + } + } + + if (escaped && (ch === "O" || ch === "[")) { + // ANSI escape sequence + let code = ch; + let modifier = 0; + + if (ch === "O") { + // ESC O letter + // ESC O modifier letter + s += ch = yield; + + if (ch >= "0" && ch <= "9") { + modifier = (ch >> 0) - 1; + s += ch = yield; + } + + code += ch; + } else if (ch === "[") { + // ESC [ letter + // ESC [ modifier letter + // ESC [ [ modifier letter + // ESC [ [ num char + s += ch = yield; + + if (ch === "[") { + // \x1b[[A + // ^--- escape codes might have a second bracket + code += ch; + s += ch = yield; + } + + /* + * Here and later we try to buffer just enough data to get + * a complete ascii sequence. + * + * We have basically two classes of ascii characters to process: + * + * 1. `\x1b[24;5~` should be parsed as { code: '[24~', modifier: 5 } + * + * This particular example is featuring Ctrl+F12 in xterm. + * + * - `;5` part is optional, e.g. it could be `\x1b[24~` + * - first part can contain one or two digits + * + * So the generic regexp is like /^\d\d?(;\d)?[~^$]$/ + * + * 2. `\x1b[1;5H` should be parsed as { code: '[H', modifier: 5 } + * + * This particular example is featuring Ctrl+Home in xterm. + * + * - `1;5` part is optional, e.g. it could be `\x1b[H` + * - `1;` part is optional, e.g. it could be `\x1b[5H` + * + * So the generic regexp is like /^((\d;)?\d)?[A-Za-z]$/ + */ + const cmdStart = s.length - 1; + + // Skip one or two leading digits + if (ch >= "0" && ch <= "9") { + s += ch = yield; + + if (ch >= "0" && ch <= "9") { + s += ch = yield; + } + } + + // skip modifier + if (ch === ";") { + s += ch = yield; + + if (ch >= "0" && ch <= "9") { + s += yield; + } + } + + /* + * We buffered enough data, now trying to extract code + * and modifier from it + */ + const cmd = s.slice(cmdStart); + let match; + + if ((match = cmd.match(/^(\d\d?)(;(\d))?([~^$])$/))) { + code += match[1] + match[4]; + modifier = (match[3] || 1) - 1; + } else if ( + (match = cmd.match(/^((\d;)?(\d))?([A-Za-z])$/)) + ) { + code += match[4]; + modifier = (match[3] || 1) - 1; + } else { + code += cmd; + } + } + + // Parse the key modifier + key.ctrl = !!(modifier & 4); + key.meta = !!(modifier & 10); + key.shift = !!(modifier & 1); + key.code = code; + + // Parse the key itself + switch (code) { + /* xterm/gnome ESC [ letter (with modifier) */ + case "[P": + key.name = "f1"; + break; + case "[Q": + key.name = "f2"; + break; + case "[R": + key.name = "f3"; + break; + case "[S": + key.name = "f4"; + break; + + /* xterm/gnome ESC O letter (without modifier) */ + + case "OP": + key.name = "f1"; + break; + case "OQ": + key.name = "f2"; + break; + case "OR": + key.name = "f3"; + break; + case "OS": + key.name = "f4"; + break; + + /* xterm/rxvt ESC [ number ~ */ + + case "[11~": + key.name = "f1"; + break; + case "[12~": + key.name = "f2"; + break; + case "[13~": + key.name = "f3"; + break; + case "[14~": + key.name = "f4"; + break; + + /* from Cygwin and used in libuv */ + + case "[[A": + key.name = "f1"; + break; + case "[[B": + key.name = "f2"; + break; + case "[[C": + key.name = "f3"; + break; + case "[[D": + key.name = "f4"; + break; + case "[[E": + key.name = "f5"; + break; + + /* common */ + + case "[15~": + key.name = "f5"; + break; + case "[17~": + key.name = "f6"; + break; + case "[18~": + key.name = "f7"; + break; + case "[19~": + key.name = "f8"; + break; + case "[20~": + key.name = "f9"; + break; + case "[21~": + key.name = "f10"; + break; + case "[23~": + key.name = "f11"; + break; + case "[24~": + key.name = "f12"; + break; + + /* xterm ESC [ letter */ + + case "[A": + key.name = "up"; + break; + case "[B": + key.name = "down"; + break; + case "[C": + key.name = "right"; + break; + case "[D": + key.name = "left"; + break; + case "[E": + key.name = "clear"; + break; + case "[F": + key.name = "end"; + break; + case "[H": + key.name = "home"; + break; + + /* xterm/gnome ESC O letter */ + + case "OA": + key.name = "up"; + break; + case "OB": + key.name = "down"; + break; + case "OC": + key.name = "right"; + break; + case "OD": + key.name = "left"; + break; + case "OE": + key.name = "clear"; + break; + case "OF": + key.name = "end"; + break; + case "OH": + key.name = "home"; + break; + + /* xterm/rxvt ESC [ number ~ */ + + case "[1~": + key.name = "home"; + break; + case "[2~": + key.name = "insert"; + break; + case "[3~": + key.name = "delete"; + break; + case "[4~": + key.name = "end"; + break; + case "[5~": + key.name = "pageup"; + break; + case "[6~": + key.name = "pagedown"; + break; + + /* putty */ + + case "[[5~": + key.name = "pageup"; + break; + case "[[6~": + key.name = "pagedown"; + break; + + /* rxvt */ + + case "[7~": + key.name = "home"; + break; + case "[8~": + key.name = "end"; + break; + + /* rxvt keys with modifiers */ + + case "[a": + key.name = "up"; + key.shift = true; + break; + case "[b": + key.name = "down"; + key.shift = true; + break; + case "[c": + key.name = "right"; + key.shift = true; + break; + case "[d": + key.name = "left"; + key.shift = true; + break; + case "[e": + key.name = "clear"; + key.shift = true; + break; + + case "[2$": + key.name = "insert"; + key.shift = true; + break; + case "[3$": + key.name = "delete"; + key.shift = true; + break; + case "[5$": + key.name = "pageup"; + key.shift = true; + break; + case "[6$": + key.name = "pagedown"; + key.shift = true; + break; + case "[7$": + key.name = "home"; + key.shift = true; + break; + case "[8$": + key.name = "end"; + key.shift = true; + break; + + case "Oa": + key.name = "up"; + key.ctrl = true; + break; + case "Ob": + key.name = "down"; + key.ctrl = true; + break; + case "Oc": + key.name = "right"; + key.ctrl = true; + break; + case "Od": + key.name = "left"; + key.ctrl = true; + break; + case "Oe": + key.name = "clear"; + key.ctrl = true; + break; + + case "[2^": + key.name = "insert"; + key.ctrl = true; + break; + case "[3^": + key.name = "delete"; + key.ctrl = true; + break; + case "[5^": + key.name = "pageup"; + key.ctrl = true; + break; + case "[6^": + key.name = "pagedown"; + key.ctrl = true; + break; + case "[7^": + key.name = "home"; + key.ctrl = true; + break; + case "[8^": + key.name = "end"; + key.ctrl = true; + break; + + /* misc. */ + + case "[Z": + key.name = "tab"; + key.shift = true; + break; + default: + key.name = "undefined"; + break; + } + } else if (ch === "\r") { + // carriage return + key.name = "return"; + key.meta = escaped; + } else if (ch === "\n") { + // Enter, should have been called linefeed + key.name = "enter"; + key.meta = escaped; + } else if (ch === "\t") { + // tab + key.name = "tab"; + key.meta = escaped; + } else if (ch === "\b" || ch === "\x7f") { + // backspace or ctrl+h + key.name = "backspace"; + key.meta = escaped; + } else if (ch === kEscape) { + // escape key + key.name = "escape"; + key.meta = escaped; + } else if (ch === " ") { + key.name = "space"; + key.meta = escaped; + } else if (!escaped && ch <= "\x1a") { + // ctrl+letter + key.name = String.fromCharCode( + ch.charCodeAt() + "a".charCodeAt() - 1, + ); + key.ctrl = true; + } else if (/^[0-9A-Za-z]$/.test(ch)) { + // Letter, number, shift+letter + key.name = ch.toLowerCase(); + key.shift = /^[A-Z]$/.test(ch); + key.meta = escaped; + } else if (escaped) { + // Escape sequence timeout + key.name = ch.length ? undefined : "escape"; + key.meta = true; + } + + key.sequence = s; + + if (s.length !== 0 && (key.name !== undefined || escaped)) { + /* Named character or sequence */ + stream.emit("keypress", escaped ? undefined : s, key); + } else if (charLengthAt(s, 0) === s.length) { + /* Single unnamed character, e.g. "." */ + stream.emit("keypress", s, key); + } + /* Unrecognized or broken escape sequence, don't emit anything */ + } +} + +// This runs in O(n log n). +export function commonPrefix(strings) { + if (strings.length === 1) { + return strings[0]; + } + const sorted = strings.slice().sort(); + const min = sorted[0]; + const max = sorted[sorted.length - 1]; + for (let i = 0; i < min.length; i++) { + if (min[i] !== max[i]) { + return min.slice(0, i); + } + } + return min; +} + +export default { + CSI, + charLengthAt, + charLengthLeft, + emitKeys, + commonPrefix, + kSubstringSearch, +}; diff --git a/ext/node/polyfills/internal/stream_base_commons.ts b/ext/node/polyfills/internal/stream_base_commons.ts new file mode 100644 index 000000000..dd1c74d0f --- /dev/null +++ b/ext/node/polyfills/internal/stream_base_commons.ts @@ -0,0 +1,355 @@ +// Copyright 2018-2023 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 { ownerSymbol } from "internal:deno_node/polyfills/internal/async_hooks.ts"; +import { + kArrayBufferOffset, + kBytesWritten, + kLastWriteWasAsync, + LibuvStreamWrap, + streamBaseState, + WriteWrap, +} from "internal:deno_node/polyfills/internal_binding/stream_wrap.ts"; +import { isUint8Array } from "internal:deno_node/polyfills/internal/util/types.ts"; +import { errnoException } from "internal:deno_node/polyfills/internal/errors.ts"; +import { + getTimerDuration, + kTimeout, +} from "internal:deno_node/polyfills/internal/timers.mjs"; +import { setUnrefTimeout } from "internal:deno_node/polyfills/timers.ts"; +import { validateFunction } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { codeMap } from "internal:deno_node/polyfills/internal_binding/uv.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; + +export const kMaybeDestroy = Symbol("kMaybeDestroy"); +export const kUpdateTimer = Symbol("kUpdateTimer"); +export const kAfterAsyncWrite = Symbol("kAfterAsyncWrite"); +export const kHandle = Symbol("kHandle"); +export const kSession = Symbol("kSession"); +export const kBuffer = Symbol("kBuffer"); +export const kBufferGen = Symbol("kBufferGen"); +export const kBufferCb = Symbol("kBufferCb"); + +// deno-lint-ignore no-explicit-any +function handleWriteReq(req: any, data: any, encoding: string) { + const { handle } = req; + + switch (encoding) { + case "buffer": { + const ret = handle.writeBuffer(req, data); + + if (streamBaseState[kLastWriteWasAsync]) { + req.buffer = data; + } + + return ret; + } + case "latin1": + case "binary": + return handle.writeLatin1String(req, data); + case "utf8": + case "utf-8": + return handle.writeUtf8String(req, data); + case "ascii": + return handle.writeAsciiString(req, data); + case "ucs2": + case "ucs-2": + case "utf16le": + case "utf-16le": + return handle.writeUcs2String(req, data); + default: { + const buffer = Buffer.from(data, encoding); + const ret = handle.writeBuffer(req, buffer); + + if (streamBaseState[kLastWriteWasAsync]) { + req.buffer = buffer; + } + + return ret; + } + } +} + +// deno-lint-ignore no-explicit-any +function onWriteComplete(this: any, status: number) { + let stream = this.handle[ownerSymbol]; + + if (stream.constructor.name === "ReusedHandle") { + stream = stream.handle; + } + + if (stream.destroyed) { + if (typeof this.callback === "function") { + this.callback(null); + } + + return; + } + + if (status < 0) { + const ex = errnoException(status, "write", this.error); + + if (typeof this.callback === "function") { + this.callback(ex); + } else { + stream.destroy(ex); + } + + return; + } + + stream[kUpdateTimer](); + stream[kAfterAsyncWrite](this); + + if (typeof this.callback === "function") { + this.callback(null); + } +} + +function createWriteWrap( + handle: LibuvStreamWrap, + callback: (err?: Error | null) => void, +) { + const req = new WriteWrap<LibuvStreamWrap>(); + + req.handle = handle; + req.oncomplete = onWriteComplete; + req.async = false; + req.bytes = 0; + req.buffer = null; + req.callback = callback; + + return req; +} + +export function writevGeneric( + // deno-lint-ignore no-explicit-any + owner: any, + // deno-lint-ignore no-explicit-any + data: any, + cb: (err?: Error | null) => void, +) { + const req = createWriteWrap(owner[kHandle], cb); + const allBuffers = data.allBuffers; + let chunks; + + if (allBuffers) { + chunks = data; + + for (let i = 0; i < data.length; i++) { + data[i] = data[i].chunk; + } + } else { + chunks = new Array(data.length << 1); + + for (let i = 0; i < data.length; i++) { + const entry = data[i]; + chunks[i * 2] = entry.chunk; + chunks[i * 2 + 1] = entry.encoding; + } + } + + const err = req.handle.writev(req, chunks, allBuffers); + + // Retain chunks + if (err === 0) { + req._chunks = chunks; + } + + afterWriteDispatched(req, err, cb); + + return req; +} + +export function writeGeneric( + // deno-lint-ignore no-explicit-any + owner: any, + // deno-lint-ignore no-explicit-any + data: any, + encoding: string, + cb: (err?: Error | null) => void, +) { + const req = createWriteWrap(owner[kHandle], cb); + const err = handleWriteReq(req, data, encoding); + + afterWriteDispatched(req, err, cb); + + return req; +} + +function afterWriteDispatched( + // deno-lint-ignore no-explicit-any + req: any, + err: number, + cb: (err?: Error | null) => void, +) { + req.bytes = streamBaseState[kBytesWritten]; + req.async = !!streamBaseState[kLastWriteWasAsync]; + + if (err !== 0) { + return cb(errnoException(err, "write", req.error)); + } + + if (!req.async && typeof req.callback === "function") { + req.callback(); + } +} + +// Here we differ from Node slightly. Node makes use of the `kReadBytesOrError` +// entry of the `streamBaseState` array from the `stream_wrap` internal binding. +// Here we pass the `nread` value directly to this method as async Deno APIs +// don't grant us the ability to rely on some mutable array entry setting. +export function onStreamRead( + // deno-lint-ignore no-explicit-any + this: any, + arrayBuffer: Uint8Array, + nread: number, +) { + // deno-lint-ignore no-this-alias + const handle = this; + + let stream = this[ownerSymbol]; + + if (stream.constructor.name === "ReusedHandle") { + stream = stream.handle; + } + + stream[kUpdateTimer](); + + if (nread > 0 && !stream.destroyed) { + let ret; + let result; + const userBuf = stream[kBuffer]; + + if (userBuf) { + result = stream[kBufferCb](nread, userBuf) !== false; + const bufGen = stream[kBufferGen]; + + if (bufGen !== null) { + const nextBuf = bufGen(); + + if (isUint8Array(nextBuf)) { + stream[kBuffer] = ret = nextBuf; + } + } + } else { + const offset = streamBaseState[kArrayBufferOffset]; + const buf = Buffer.from(arrayBuffer, offset, nread); + result = stream.push(buf); + } + + if (!result) { + handle.reading = false; + + if (!stream.destroyed) { + const err = handle.readStop(); + + if (err) { + stream.destroy(errnoException(err, "read")); + } + } + } + + return ret; + } + + if (nread === 0) { + return; + } + + if (nread !== codeMap.get("EOF")) { + // CallJSOnreadMethod expects the return value to be a buffer. + // Ref: https://github.com/nodejs/node/pull/34375 + stream.destroy(errnoException(nread, "read")); + + return; + } + + // Defer this until we actually emit end + if (stream._readableState.endEmitted) { + if (stream[kMaybeDestroy]) { + stream[kMaybeDestroy](); + } + } else { + if (stream[kMaybeDestroy]) { + stream.on("end", stream[kMaybeDestroy]); + } + + if (handle.readStop) { + const err = handle.readStop(); + + if (err) { + // CallJSOnreadMethod expects the return value to be a buffer. + // Ref: https://github.com/nodejs/node/pull/34375 + stream.destroy(errnoException(err, "read")); + + return; + } + } + + // Push a null to signal the end of data. + // Do it before `maybeDestroy` for correct order of events: + // `end` -> `close` + stream.push(null); + stream.read(0); + } +} + +export function setStreamTimeout( + // deno-lint-ignore no-explicit-any + this: any, + msecs: number, + callback?: () => void, +) { + if (this.destroyed) { + return this; + } + + this.timeout = msecs; + + // Type checking identical to timers.enroll() + msecs = getTimerDuration(msecs, "msecs"); + + // Attempt to clear an existing timer in both cases - + // even if it will be rescheduled we don't want to leak an existing timer. + clearTimeout(this[kTimeout]); + + if (msecs === 0) { + if (callback !== undefined) { + validateFunction(callback, "callback"); + this.removeListener("timeout", callback); + } + } else { + this[kTimeout] = setUnrefTimeout(this._onTimeout.bind(this), msecs); + + if (this[kSession]) { + this[kSession][kUpdateTimer](); + } + + if (callback !== undefined) { + validateFunction(callback, "callback"); + this.once("timeout", callback); + } + } + + return this; +} diff --git a/ext/node/polyfills/internal/streams/add-abort-signal.mjs b/ext/node/polyfills/internal/streams/add-abort-signal.mjs new file mode 100644 index 000000000..5d7512f1c --- /dev/null +++ b/ext/node/polyfills/internal/streams/add-abort-signal.mjs @@ -0,0 +1,48 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { AbortError, ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; +import eos from "internal:deno_node/polyfills/internal/streams/end-of-stream.mjs"; + +// This method is inlined here for readable-stream +// It also does not allow for signal to not exist on the stream +// https://github.com/nodejs/node/pull/36061#discussion_r533718029 +const validateAbortSignal = (signal, name) => { + if ( + typeof signal !== "object" || + !("aborted" in signal) + ) { + throw new ERR_INVALID_ARG_TYPE(name, "AbortSignal", signal); + } +}; + +function isStream(obj) { + return !!(obj && typeof obj.pipe === "function"); +} + +function addAbortSignal(signal, stream) { + validateAbortSignal(signal, "signal"); + if (!isStream(stream)) { + throw new ERR_INVALID_ARG_TYPE("stream", "stream.Stream", stream); + } + return addAbortSignalNoValidate(signal, stream); +} +function addAbortSignalNoValidate(signal, stream) { + if (typeof signal !== "object" || !("aborted" in signal)) { + return stream; + } + const onAbort = () => { + stream.destroy(new AbortError()); + }; + if (signal.aborted) { + onAbort(); + } else { + signal.addEventListener("abort", onAbort); + eos(stream, () => signal.removeEventListener("abort", onAbort)); + } + return stream; +} + +export default { addAbortSignal, addAbortSignalNoValidate }; +export { addAbortSignal, addAbortSignalNoValidate }; diff --git a/ext/node/polyfills/internal/streams/buffer_list.mjs b/ext/node/polyfills/internal/streams/buffer_list.mjs new file mode 100644 index 000000000..3016ffba5 --- /dev/null +++ b/ext/node/polyfills/internal/streams/buffer_list.mjs @@ -0,0 +1,188 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { inspect } from "internal:deno_node/polyfills/internal/util/inspect.mjs"; + +class BufferList { + constructor() { + this.head = null; + this.tail = null; + this.length = 0; + } + + push(v) { + const entry = { data: v, next: null }; + if (this.length > 0) { + this.tail.next = entry; + } else { + this.head = entry; + } + this.tail = entry; + ++this.length; + } + + unshift(v) { + const entry = { data: v, next: this.head }; + if (this.length === 0) { + this.tail = entry; + } + this.head = entry; + ++this.length; + } + + shift() { + if (this.length === 0) { + return; + } + const ret = this.head.data; + if (this.length === 1) { + this.head = this.tail = null; + } else { + this.head = this.head.next; + } + --this.length; + return ret; + } + + clear() { + this.head = this.tail = null; + this.length = 0; + } + + join(s) { + if (this.length === 0) { + return ""; + } + let p = this.head; + let ret = "" + p.data; + while (p = p.next) { + ret += s + p.data; + } + return ret; + } + + concat(n) { + if (this.length === 0) { + return Buffer.alloc(0); + } + const ret = Buffer.allocUnsafe(n >>> 0); + let p = this.head; + let i = 0; + while (p) { + ret.set(p.data, i); + i += p.data.length; + p = p.next; + } + return ret; + } + + // Consumes a specified amount of bytes or characters from the buffered data. + consume(n, hasStrings) { + const data = this.head.data; + if (n < data.length) { + // `slice` is the same for buffers and strings. + const slice = data.slice(0, n); + this.head.data = data.slice(n); + return slice; + } + if (n === data.length) { + // First chunk is a perfect match. + return this.shift(); + } + // Result spans more than one buffer. + return hasStrings ? this._getString(n) : this._getBuffer(n); + } + + first() { + return this.head.data; + } + + *[Symbol.iterator]() { + for (let p = this.head; p; p = p.next) { + yield p.data; + } + } + + // Consumes a specified amount of characters from the buffered data. + _getString(n) { + let ret = ""; + let p = this.head; + let c = 0; + do { + const str = p.data; + if (n > str.length) { + ret += str; + n -= str.length; + } else { + if (n === str.length) { + ret += str; + ++c; + if (p.next) { + this.head = p.next; + } else { + this.head = this.tail = null; + } + } else { + ret += str.slice(0, n); + this.head = p; + p.data = str.slice(n); + } + break; + } + ++c; + } while (p = p.next); + this.length -= c; + return ret; + } + + // Consumes a specified amount of bytes from the buffered data. + _getBuffer(n) { + const ret = Buffer.allocUnsafe(n); + const retLen = n; + let p = this.head; + let c = 0; + do { + const buf = p.data; + if (n > buf.length) { + ret.set(buf, retLen - n); + n -= buf.length; + } else { + if (n === buf.length) { + ret.set(buf, retLen - n); + ++c; + if (p.next) { + this.head = p.next; + } else { + this.head = this.tail = null; + } + } else { + ret.set( + new Uint8Array(buf.buffer, buf.byteOffset, n), + retLen - n, + ); + this.head = p; + p.data = buf.slice(n); + } + break; + } + ++c; + } while (p = p.next); + this.length -= c; + return ret; + } + + // Make sure the linked list only shows the minimal necessary information. + [inspect.custom](_, options) { + return inspect(this, { + ...options, + // Only inspect one level. + depth: 0, + // It should not recurse. + customInspect: false, + }); + } +} + +export default BufferList; diff --git a/ext/node/polyfills/internal/streams/destroy.mjs b/ext/node/polyfills/internal/streams/destroy.mjs new file mode 100644 index 000000000..b065f2119 --- /dev/null +++ b/ext/node/polyfills/internal/streams/destroy.mjs @@ -0,0 +1,320 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { aggregateTwoErrors, ERR_MULTIPLE_CALLBACK } from "internal:deno_node/polyfills/internal/errors.ts"; +import * as process from "internal:deno_node/polyfills/_process/process.ts"; + +const kDestroy = Symbol("kDestroy"); +const kConstruct = Symbol("kConstruct"); + +function checkError(err, w, r) { + if (err) { + // Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364 + err.stack; // eslint-disable-line no-unused-expressions + + if (w && !w.errored) { + w.errored = err; + } + if (r && !r.errored) { + r.errored = err; + } + } +} + +// Backwards compat. cb() is undocumented and unused in core but +// unfortunately might be used by modules. +function destroy(err, cb) { + const r = this._readableState; + const w = this._writableState; + // With duplex streams we use the writable side for state. + const s = w || r; + + if ((w && w.destroyed) || (r && r.destroyed)) { + if (typeof cb === "function") { + cb(); + } + + return this; + } + + // We set destroyed to true before firing error callbacks in order + // to make it re-entrance safe in case destroy() is called within callbacks + checkError(err, w, r); + + if (w) { + w.destroyed = true; + } + if (r) { + r.destroyed = true; + } + + // If still constructing then defer calling _destroy. + if (!s.constructed) { + this.once(kDestroy, function (er) { + _destroy(this, aggregateTwoErrors(er, err), cb); + }); + } else { + _destroy(this, err, cb); + } + + return this; +} + +function _destroy(self, err, cb) { + let called = false; + + function onDestroy(err) { + if (called) { + return; + } + called = true; + + const r = self._readableState; + const w = self._writableState; + + checkError(err, w, r); + + if (w) { + w.closed = true; + } + if (r) { + r.closed = true; + } + + if (typeof cb === "function") { + cb(err); + } + + if (err) { + process.nextTick(emitErrorCloseNT, self, err); + } else { + process.nextTick(emitCloseNT, self); + } + } + try { + const result = self._destroy(err || null, onDestroy); + if (result != null) { + const then = result.then; + if (typeof then === "function") { + then.call( + result, + function () { + process.nextTick(onDestroy, null); + }, + function (err) { + process.nextTick(onDestroy, err); + }, + ); + } + } + } catch (err) { + onDestroy(err); + } +} + +function emitErrorCloseNT(self, err) { + emitErrorNT(self, err); + emitCloseNT(self); +} + +function emitCloseNT(self) { + const r = self._readableState; + const w = self._writableState; + + if (w) { + w.closeEmitted = true; + } + if (r) { + r.closeEmitted = true; + } + + if ((w && w.emitClose) || (r && r.emitClose)) { + self.emit("close"); + } +} + +function emitErrorNT(self, err) { + const r = self._readableState; + const w = self._writableState; + + if ((w && w.errorEmitted) || (r && r.errorEmitted)) { + return; + } + + if (w) { + w.errorEmitted = true; + } + if (r) { + r.errorEmitted = true; + } + + self.emit("error", err); +} + +function undestroy() { + const r = this._readableState; + const w = this._writableState; + + if (r) { + r.constructed = true; + r.closed = false; + r.closeEmitted = false; + r.destroyed = false; + r.errored = null; + r.errorEmitted = false; + r.reading = false; + r.ended = false; + r.endEmitted = false; + } + + if (w) { + w.constructed = true; + w.destroyed = false; + w.closed = false; + w.closeEmitted = false; + w.errored = null; + w.errorEmitted = false; + w.ended = false; + w.ending = false; + w.finalCalled = false; + w.prefinished = false; + w.finished = false; + } +} + +function errorOrDestroy(stream, err, sync) { + // We have tests that rely on errors being emitted + // in the same tick, so changing this is semver major. + // For now when you opt-in to autoDestroy we allow + // the error to be emitted nextTick. In a future + // semver major update we should change the default to this. + + const r = stream._readableState; + const w = stream._writableState; + + if ((w && w.destroyed) || (r && r.destroyed)) { + return this; + } + + if ((r && r.autoDestroy) || (w && w.autoDestroy)) { + stream.destroy(err); + } else if (err) { + // Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364 + err.stack; // eslint-disable-line no-unused-expressions + + if (w && !w.errored) { + w.errored = err; + } + if (r && !r.errored) { + r.errored = err; + } + if (sync) { + process.nextTick(emitErrorNT, stream, err); + } else { + emitErrorNT(stream, err); + } + } +} + +function construct(stream, cb) { + if (typeof stream._construct !== "function") { + return; + } + + const r = stream._readableState; + const w = stream._writableState; + + if (r) { + r.constructed = false; + } + if (w) { + w.constructed = false; + } + + stream.once(kConstruct, cb); + + if (stream.listenerCount(kConstruct) > 1) { + // Duplex + return; + } + + process.nextTick(constructNT, stream); +} + +function constructNT(stream) { + let called = false; + + function onConstruct(err) { + if (called) { + errorOrDestroy(stream, err ?? new ERR_MULTIPLE_CALLBACK()); + return; + } + called = true; + + const r = stream._readableState; + const w = stream._writableState; + const s = w || r; + + if (r) { + r.constructed = true; + } + if (w) { + w.constructed = true; + } + + if (s.destroyed) { + stream.emit(kDestroy, err); + } else if (err) { + errorOrDestroy(stream, err, true); + } else { + process.nextTick(emitConstructNT, stream); + } + } + + try { + const result = stream._construct(onConstruct); + if (result != null) { + const then = result.then; + if (typeof then === "function") { + then.call( + result, + function () { + process.nextTick(onConstruct, null); + }, + function (err) { + process.nextTick(onConstruct, err); + }, + ); + } + } + } catch (err) { + onConstruct(err); + } +} + +function emitConstructNT(stream) { + stream.emit(kConstruct); +} + +function isRequest(stream) { + return stream && stream.setHeader && typeof stream.abort === "function"; +} + +// Normalize destroy for legacy. +function destroyer(stream, err) { + if (!stream) return; + if (isRequest(stream)) return stream.abort(); + if (isRequest(stream.req)) return stream.req.abort(); + if (typeof stream.destroy === "function") return stream.destroy(err); + if (typeof stream.close === "function") return stream.close(); +} + +export default { + construct, + destroyer, + destroy, + undestroy, + errorOrDestroy, +}; +export { construct, destroy, destroyer, errorOrDestroy, undestroy }; diff --git a/ext/node/polyfills/internal/streams/duplex.mjs b/ext/node/polyfills/internal/streams/duplex.mjs new file mode 100644 index 000000000..b2086d467 --- /dev/null +++ b/ext/node/polyfills/internal/streams/duplex.mjs @@ -0,0 +1,9 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { Duplex } from "internal:deno_node/polyfills/_stream.mjs"; +const { from, fromWeb, toWeb } = Duplex; + +export default Duplex; +export { from, fromWeb, toWeb }; diff --git a/ext/node/polyfills/internal/streams/end-of-stream.mjs b/ext/node/polyfills/internal/streams/end-of-stream.mjs new file mode 100644 index 000000000..b5c380d56 --- /dev/null +++ b/ext/node/polyfills/internal/streams/end-of-stream.mjs @@ -0,0 +1,229 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { AbortError, ERR_STREAM_PREMATURE_CLOSE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { once } from "internal:deno_node/polyfills/internal/util.mjs"; +import { + validateAbortSignal, + validateFunction, + validateObject, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import * as process from "internal:deno_node/polyfills/_process/process.ts"; + +function isRequest(stream) { + return stream.setHeader && typeof stream.abort === "function"; +} + +function isServerResponse(stream) { + return ( + typeof stream._sent100 === "boolean" && + typeof stream._removedConnection === "boolean" && + typeof stream._removedContLen === "boolean" && + typeof stream._removedTE === "boolean" && + typeof stream._closed === "boolean" + ); +} + +function isReadable(stream) { + return typeof stream.readable === "boolean" || + typeof stream.readableEnded === "boolean" || + !!stream._readableState; +} + +function isWritable(stream) { + return typeof stream.writable === "boolean" || + typeof stream.writableEnded === "boolean" || + !!stream._writableState; +} + +function isWritableFinished(stream) { + if (stream.writableFinished) return true; + const wState = stream._writableState; + if (!wState || wState.errored) return false; + return wState.finished || (wState.ended && wState.length === 0); +} + +const nop = () => {}; + +function isReadableEnded(stream) { + if (stream.readableEnded) return true; + const rState = stream._readableState; + if (!rState || rState.errored) return false; + return rState.endEmitted || (rState.ended && rState.length === 0); +} + +function eos(stream, options, callback) { + if (arguments.length === 2) { + callback = options; + options = {}; + } else if (options == null) { + options = {}; + } else { + validateObject(options, "options"); + } + validateFunction(callback, "callback"); + validateAbortSignal(options.signal, "options.signal"); + + callback = once(callback); + + const readable = options.readable || + (options.readable !== false && isReadable(stream)); + const writable = options.writable || + (options.writable !== false && isWritable(stream)); + + const wState = stream._writableState; + const rState = stream._readableState; + const state = wState || rState; + + const onlegacyfinish = () => { + if (!stream.writable) onfinish(); + }; + + // TODO (ronag): Improve soft detection to include core modules and + // common ecosystem modules that do properly emit 'close' but fail + // this generic check. + let willEmitClose = isServerResponse(stream) || ( + state && + state.autoDestroy && + state.emitClose && + state.closed === false && + isReadable(stream) === readable && + isWritable(stream) === writable + ); + + let writableFinished = stream.writableFinished || + (wState && wState.finished); + const onfinish = () => { + writableFinished = true; + // Stream should not be destroyed here. If it is that + // means that user space is doing something differently and + // we cannot trust willEmitClose. + if (stream.destroyed) willEmitClose = false; + + if (willEmitClose && (!stream.readable || readable)) return; + if (!readable || readableEnded) callback.call(stream); + }; + + let readableEnded = stream.readableEnded || + (rState && rState.endEmitted); + const onend = () => { + readableEnded = true; + // Stream should not be destroyed here. If it is that + // means that user space is doing something differently and + // we cannot trust willEmitClose. + if (stream.destroyed) willEmitClose = false; + + if (willEmitClose && (!stream.writable || writable)) return; + if (!writable || writableFinished) callback.call(stream); + }; + + const onerror = (err) => { + callback.call(stream, err); + }; + + const onclose = () => { + if (readable && !readableEnded) { + if (!isReadableEnded(stream)) { + return callback.call(stream, new ERR_STREAM_PREMATURE_CLOSE()); + } + } + if (writable && !writableFinished) { + if (!isWritableFinished(stream)) { + return callback.call(stream, new ERR_STREAM_PREMATURE_CLOSE()); + } + } + callback.call(stream); + }; + + const onrequest = () => { + stream.req.on("finish", onfinish); + }; + + if (isRequest(stream)) { + stream.on("complete", onfinish); + if (!willEmitClose) { + stream.on("abort", onclose); + } + if (stream.req) onrequest(); + else stream.on("request", onrequest); + } else if (writable && !wState) { // legacy streams + stream.on("end", onlegacyfinish); + stream.on("close", onlegacyfinish); + } + + // Not all streams will emit 'close' after 'aborted'. + if (!willEmitClose && typeof stream.aborted === "boolean") { + stream.on("aborted", onclose); + } + + stream.on("end", onend); + stream.on("finish", onfinish); + if (options.error !== false) stream.on("error", onerror); + stream.on("close", onclose); + + // _closed is for OutgoingMessage which is not a proper Writable. + const closed = (!wState && !rState && stream._closed === true) || ( + (wState && wState.closed) || + (rState && rState.closed) || + (wState && wState.errorEmitted) || + (rState && rState.errorEmitted) || + (rState && stream.req && stream.aborted) || + ( + (!wState || !willEmitClose || typeof wState.closed !== "boolean") && + (!rState || !willEmitClose || typeof rState.closed !== "boolean") && + (!writable || (wState && wState.finished)) && + (!readable || (rState && rState.endEmitted)) + ) + ); + + if (closed) { + // TODO(ronag): Re-throw error if errorEmitted? + // TODO(ronag): Throw premature close as if finished was called? + // before being closed? i.e. if closed but not errored, ended or finished. + // TODO(ronag): Throw some kind of error? Does it make sense + // to call finished() on a "finished" stream? + // TODO(ronag): willEmitClose? + process.nextTick(() => { + callback(); + }); + } + + const cleanup = () => { + callback = nop; + stream.removeListener("aborted", onclose); + stream.removeListener("complete", onfinish); + stream.removeListener("abort", onclose); + stream.removeListener("request", onrequest); + if (stream.req) stream.req.removeListener("finish", onfinish); + stream.removeListener("end", onlegacyfinish); + stream.removeListener("close", onlegacyfinish); + stream.removeListener("finish", onfinish); + stream.removeListener("end", onend); + stream.removeListener("error", onerror); + stream.removeListener("close", onclose); + }; + + if (options.signal && !closed) { + const abort = () => { + // Keep it because cleanup removes it. + const endCallback = callback; + cleanup(); + endCallback.call(stream, new AbortError()); + }; + if (options.signal.aborted) { + process.nextTick(abort); + } else { + const originalCallback = callback; + callback = once((...args) => { + options.signal.removeEventListener("abort", abort); + originalCallback.apply(stream, args); + }); + options.signal.addEventListener("abort", abort); + } + } + + return cleanup; +} + +export default eos; diff --git a/ext/node/polyfills/internal/streams/lazy_transform.mjs b/ext/node/polyfills/internal/streams/lazy_transform.mjs new file mode 100644 index 000000000..2bb93bd91 --- /dev/null +++ b/ext/node/polyfills/internal/streams/lazy_transform.mjs @@ -0,0 +1,53 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { getDefaultEncoding } from "internal:deno_node/polyfills/internal/crypto/util.ts"; +import stream from "internal:deno_node/polyfills/stream.ts"; + +function LazyTransform(options) { + this._options = options; +} +Object.setPrototypeOf(LazyTransform.prototype, stream.Transform.prototype); +Object.setPrototypeOf(LazyTransform, stream.Transform); + +function makeGetter(name) { + return function () { + stream.Transform.call(this, this._options); + this._writableState.decodeStrings = false; + + if (!this._options || !this._options.defaultEncoding) { + this._writableState.defaultEncoding = getDefaultEncoding(); + } + + return this[name]; + }; +} + +function makeSetter(name) { + return function (val) { + Object.defineProperty(this, name, { + value: val, + enumerable: true, + configurable: true, + writable: true, + }); + }; +} + +Object.defineProperties(LazyTransform.prototype, { + _readableState: { + get: makeGetter("_readableState"), + set: makeSetter("_readableState"), + configurable: true, + enumerable: true, + }, + _writableState: { + get: makeGetter("_writableState"), + set: makeSetter("_writableState"), + configurable: true, + enumerable: true, + }, +}); + +export default LazyTransform; diff --git a/ext/node/polyfills/internal/streams/legacy.mjs b/ext/node/polyfills/internal/streams/legacy.mjs new file mode 100644 index 000000000..0de18956f --- /dev/null +++ b/ext/node/polyfills/internal/streams/legacy.mjs @@ -0,0 +1,113 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import EE from "internal:deno_node/polyfills/events.ts"; + +function Stream(opts) { + EE.call(this, opts); +} +Object.setPrototypeOf(Stream.prototype, EE.prototype); +Object.setPrototypeOf(Stream, EE); + +Stream.prototype.pipe = function (dest, options) { + // deno-lint-ignore no-this-alias + const source = this; + + function ondata(chunk) { + if (dest.writable && dest.write(chunk) === false && source.pause) { + source.pause(); + } + } + + source.on("data", ondata); + + function ondrain() { + if (source.readable && source.resume) { + source.resume(); + } + } + + dest.on("drain", ondrain); + + // If the 'end' option is not supplied, dest.end() will be called when + // source gets the 'end' or 'close' events. Only dest.end() once. + if (!dest._isStdio && (!options || options.end !== false)) { + source.on("end", onend); + source.on("close", onclose); + } + + let didOnEnd = false; + function onend() { + if (didOnEnd) return; + didOnEnd = true; + + dest.end(); + } + + function onclose() { + if (didOnEnd) return; + didOnEnd = true; + + if (typeof dest.destroy === "function") dest.destroy(); + } + + // Don't leave dangling pipes when there are errors. + function onerror(er) { + cleanup(); + if (EE.listenerCount(this, "error") === 0) { + this.emit("error", er); + } + } + + prependListener(source, "error", onerror); + prependListener(dest, "error", onerror); + + // Remove all the event listeners that were added. + function cleanup() { + source.removeListener("data", ondata); + dest.removeListener("drain", ondrain); + + source.removeListener("end", onend); + source.removeListener("close", onclose); + + source.removeListener("error", onerror); + dest.removeListener("error", onerror); + + source.removeListener("end", cleanup); + source.removeListener("close", cleanup); + + dest.removeListener("close", cleanup); + } + + source.on("end", cleanup); + source.on("close", cleanup); + + dest.on("close", cleanup); + dest.emit("pipe", source); + + // Allow for unix-like usage: A.pipe(B).pipe(C) + return dest; +}; + +function prependListener(emitter, event, fn) { + // Sadly this is not cacheable as some libraries bundle their own + // event emitter implementation with them. + if (typeof emitter.prependListener === "function") { + return emitter.prependListener(event, fn); + } + + // This is a hack to make sure that our error handler is attached before any + // userland ones. NEVER DO THIS. This is here only because this code needs + // to continue to work with older versions of Node.js that do not include + // the prependListener() method. The goal is to eventually remove this hack. + if (!emitter._events || !emitter._events[event]) { + emitter.on(event, fn); + } else if (Array.isArray(emitter._events[event])) { + emitter._events[event].unshift(fn); + } else { + emitter._events[event] = [fn, emitter._events[event]]; + } +} + +export { prependListener, Stream }; diff --git a/ext/node/polyfills/internal/streams/passthrough.mjs b/ext/node/polyfills/internal/streams/passthrough.mjs new file mode 100644 index 000000000..136a0484a --- /dev/null +++ b/ext/node/polyfills/internal/streams/passthrough.mjs @@ -0,0 +1,7 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { PassThrough } from "internal:deno_node/polyfills/_stream.mjs"; + +export default PassThrough; diff --git a/ext/node/polyfills/internal/streams/readable.mjs b/ext/node/polyfills/internal/streams/readable.mjs new file mode 100644 index 000000000..36133d297 --- /dev/null +++ b/ext/node/polyfills/internal/streams/readable.mjs @@ -0,0 +1,9 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { Readable } from "internal:deno_node/polyfills/_stream.mjs"; +const { ReadableState, _fromList, from, fromWeb, toWeb, wrap } = Readable; + +export default Readable; +export { _fromList, from, fromWeb, ReadableState, toWeb, wrap }; diff --git a/ext/node/polyfills/internal/streams/state.mjs b/ext/node/polyfills/internal/streams/state.mjs new file mode 100644 index 000000000..93708fe9d --- /dev/null +++ b/ext/node/polyfills/internal/streams/state.mjs @@ -0,0 +1,10 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +function getDefaultHighWaterMark(objectMode) { + return objectMode ? 16 : 16 * 1024; +} + +export default { getDefaultHighWaterMark }; +export { getDefaultHighWaterMark }; diff --git a/ext/node/polyfills/internal/streams/transform.mjs b/ext/node/polyfills/internal/streams/transform.mjs new file mode 100644 index 000000000..3fc4fa5cd --- /dev/null +++ b/ext/node/polyfills/internal/streams/transform.mjs @@ -0,0 +1,7 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { Transform } from "internal:deno_node/polyfills/_stream.mjs"; + +export default Transform; diff --git a/ext/node/polyfills/internal/streams/utils.mjs b/ext/node/polyfills/internal/streams/utils.mjs new file mode 100644 index 000000000..a575f831d --- /dev/null +++ b/ext/node/polyfills/internal/streams/utils.mjs @@ -0,0 +1,242 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +const kIsDisturbed = Symbol("kIsDisturbed"); + +function isReadableNodeStream(obj) { + return !!( + obj && + typeof obj.pipe === "function" && + typeof obj.on === "function" && + (!obj._writableState || obj._readableState?.readable !== false) && // Duplex + (!obj._writableState || obj._readableState) // Writable has .pipe. + ); +} + +function isWritableNodeStream(obj) { + return !!( + obj && + typeof obj.write === "function" && + typeof obj.on === "function" && + (!obj._readableState || obj._writableState?.writable !== false) // Duplex + ); +} + +function isDuplexNodeStream(obj) { + return !!( + obj && + (typeof obj.pipe === "function" && obj._readableState) && + typeof obj.on === "function" && + typeof obj.write === "function" + ); +} + +function isNodeStream(obj) { + return ( + obj && + ( + obj._readableState || + obj._writableState || + (typeof obj.write === "function" && typeof obj.on === "function") || + (typeof obj.pipe === "function" && typeof obj.on === "function") + ) + ); +} + +function isDestroyed(stream) { + if (!isNodeStream(stream)) return null; + const wState = stream._writableState; + const rState = stream._readableState; + const state = wState || rState; + return !!(stream.destroyed || state?.destroyed); +} + +// Have been end():d. +function isWritableEnded(stream) { + if (!isWritableNodeStream(stream)) return null; + if (stream.writableEnded === true) return true; + const wState = stream._writableState; + if (wState?.errored) return false; + if (typeof wState?.ended !== "boolean") return null; + return wState.ended; +} + +// Have emitted 'finish'. +function isWritableFinished(stream, strict) { + if (!isWritableNodeStream(stream)) return null; + if (stream.writableFinished === true) return true; + const wState = stream._writableState; + if (wState?.errored) return false; + if (typeof wState?.finished !== "boolean") return null; + return !!( + wState.finished || + (strict === false && wState.ended === true && wState.length === 0) + ); +} + +// Have been push(null):d. +function isReadableEnded(stream) { + if (!isReadableNodeStream(stream)) return null; + if (stream.readableEnded === true) return true; + const rState = stream._readableState; + if (!rState || rState.errored) return false; + if (typeof rState?.ended !== "boolean") return null; + return rState.ended; +} + +// Have emitted 'end'. +function isReadableFinished(stream, strict) { + if (!isReadableNodeStream(stream)) return null; + const rState = stream._readableState; + if (rState?.errored) return false; + if (typeof rState?.endEmitted !== "boolean") return null; + return !!( + rState.endEmitted || + (strict === false && rState.ended === true && rState.length === 0) + ); +} + +function isDisturbed(stream) { + return !!(stream && ( + stream.readableDidRead || + stream.readableAborted || + stream[kIsDisturbed] + )); +} + +function isReadable(stream) { + const r = isReadableNodeStream(stream); + if (r === null || typeof stream?.readable !== "boolean") return null; + if (isDestroyed(stream)) return false; + return r && stream.readable && !isReadableFinished(stream); +} + +function isWritable(stream) { + const r = isWritableNodeStream(stream); + if (r === null || typeof stream?.writable !== "boolean") return null; + if (isDestroyed(stream)) return false; + return r && stream.writable && !isWritableEnded(stream); +} + +function isFinished(stream, opts) { + if (!isNodeStream(stream)) { + return null; + } + + if (isDestroyed(stream)) { + return true; + } + + if (opts?.readable !== false && isReadable(stream)) { + return false; + } + + if (opts?.writable !== false && isWritable(stream)) { + return false; + } + + return true; +} + +function isClosed(stream) { + if (!isNodeStream(stream)) { + return null; + } + + const wState = stream._writableState; + const rState = stream._readableState; + + if ( + typeof wState?.closed === "boolean" || + typeof rState?.closed === "boolean" + ) { + return wState?.closed || rState?.closed; + } + + if (typeof stream._closed === "boolean" && isOutgoingMessage(stream)) { + return stream._closed; + } + + return null; +} + +function isOutgoingMessage(stream) { + return ( + typeof stream._closed === "boolean" && + typeof stream._defaultKeepAlive === "boolean" && + typeof stream._removedConnection === "boolean" && + typeof stream._removedContLen === "boolean" + ); +} + +function isServerResponse(stream) { + return ( + typeof stream._sent100 === "boolean" && + isOutgoingMessage(stream) + ); +} + +function isServerRequest(stream) { + return ( + typeof stream._consuming === "boolean" && + typeof stream._dumped === "boolean" && + stream.req?.upgradeOrConnect === undefined + ); +} + +function willEmitClose(stream) { + if (!isNodeStream(stream)) return null; + + const wState = stream._writableState; + const rState = stream._readableState; + const state = wState || rState; + + return (!state && isServerResponse(stream)) || !!( + state && + state.autoDestroy && + state.emitClose && + state.closed === false + ); +} + +export default { + isDisturbed, + kIsDisturbed, + isClosed, + isDestroyed, + isDuplexNodeStream, + isFinished, + isReadable, + isReadableNodeStream, + isReadableEnded, + isReadableFinished, + isNodeStream, + isWritable, + isWritableNodeStream, + isWritableEnded, + isWritableFinished, + isServerRequest, + isServerResponse, + willEmitClose, +}; +export { + isClosed, + isDestroyed, + isDisturbed, + isDuplexNodeStream, + isFinished, + isNodeStream, + isReadable, + isReadableEnded, + isReadableFinished, + isReadableNodeStream, + isServerRequest, + isServerResponse, + isWritable, + isWritableEnded, + isWritableFinished, + isWritableNodeStream, + kIsDisturbed, + willEmitClose, +}; diff --git a/ext/node/polyfills/internal/streams/writable.mjs b/ext/node/polyfills/internal/streams/writable.mjs new file mode 100644 index 000000000..6f4d77960 --- /dev/null +++ b/ext/node/polyfills/internal/streams/writable.mjs @@ -0,0 +1,9 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { Writable } from "internal:deno_node/polyfills/_stream.mjs"; +const { WritableState, fromWeb, toWeb } = Writable; + +export default Writable; +export { fromWeb, toWeb, WritableState }; diff --git a/ext/node/polyfills/internal/test/binding.ts b/ext/node/polyfills/internal/test/binding.ts new file mode 100644 index 000000000..996cc57aa --- /dev/null +++ b/ext/node/polyfills/internal/test/binding.ts @@ -0,0 +1,16 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. +import { getBinding } from "internal:deno_node/polyfills/internal_binding/mod.ts"; +import type { BindingName } from "internal:deno_node/polyfills/internal_binding/mod.ts"; + +export function internalBinding(name: BindingName) { + return getBinding(name); +} + +// TODO(kt3k): export actual primordials +export const primordials = {}; + +export default { + internalBinding, + primordials, +}; diff --git a/ext/node/polyfills/internal/timers.mjs b/ext/node/polyfills/internal/timers.mjs new file mode 100644 index 000000000..648fb1bc1 --- /dev/null +++ b/ext/node/polyfills/internal/timers.mjs @@ -0,0 +1,125 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { inspect } from "internal:deno_node/polyfills/internal/util/inspect.mjs"; +import { validateFunction, validateNumber } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { ERR_OUT_OF_RANGE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { emitWarning } from "internal:deno_node/polyfills/process.ts"; + +const setTimeout_ = globalThis.setTimeout; +const clearTimeout_ = globalThis.clearTimeout; +const setInterval_ = globalThis.setInterval; + +// Timeout values > TIMEOUT_MAX are set to 1. +export const TIMEOUT_MAX = 2 ** 31 - 1; + +export const kTimerId = Symbol("timerId"); +export const kTimeout = Symbol("timeout"); +const kRefed = Symbol("refed"); +const createTimer = Symbol("createTimer"); + +// Timer constructor function. +export function Timeout(callback, after, args, isRepeat, isRefed) { + if (typeof after === "number" && after > TIMEOUT_MAX) { + after = 1; + } + this._idleTimeout = after; + this._onTimeout = callback; + this._timerArgs = args; + this._isRepeat = isRepeat; + this[kRefed] = isRefed; + this[kTimerId] = this[createTimer](); +} + +Timeout.prototype[createTimer] = function () { + const callback = this._onTimeout; + const cb = (...args) => callback.bind(this)(...args); + const id = this._isRepeat + ? setInterval_(cb, this._idleTimeout, ...this._timerArgs) + : setTimeout_(cb, this._idleTimeout, ...this._timerArgs); + if (!this[kRefed]) { + Deno.unrefTimer(id); + } + return id; +}; + +// Make sure the linked list only shows the minimal necessary information. +Timeout.prototype[inspect.custom] = function (_, options) { + return inspect(this, { + ...options, + // Only inspect one level. + depth: 0, + // It should not recurse. + customInspect: false, + }); +}; + +Timeout.prototype.refresh = function () { + clearTimeout_(this[kTimerId]); + this[kTimerId] = this[createTimer](); + return this; +}; + +Timeout.prototype.unref = function () { + if (this[kRefed]) { + this[kRefed] = false; + Deno.unrefTimer(this[kTimerId]); + } + return this; +}; + +Timeout.prototype.ref = function () { + if (!this[kRefed]) { + this[kRefed] = true; + Deno.refTimer(this[kTimerId]); + } + return this; +}; + +Timeout.prototype.hasRef = function () { + return this[kRefed]; +}; + +Timeout.prototype[Symbol.toPrimitive] = function () { + return this[kTimerId]; +}; + +/** + * @param {number} msecs + * @param {string} name + * @returns + */ +export function getTimerDuration(msecs, name) { + validateNumber(msecs, name); + + if (msecs < 0 || !Number.isFinite(msecs)) { + throw new ERR_OUT_OF_RANGE(name, "a non-negative finite number", msecs); + } + + // Ensure that msecs fits into signed int32 + if (msecs > TIMEOUT_MAX) { + emitWarning( + `${msecs} does not fit into a 32-bit signed integer.` + + `\nTimer duration was truncated to ${TIMEOUT_MAX}.`, + "TimeoutOverflowWarning", + ); + + return TIMEOUT_MAX; + } + + return msecs; +} + +export function setUnrefTimeout(callback, timeout, ...args) { + validateFunction(callback, "callback"); + return new Timeout(callback, timeout, args, false, false); +} + +export default { + getTimerDuration, + kTimerId, + kTimeout, + setUnrefTimeout, + Timeout, + TIMEOUT_MAX, +}; diff --git a/ext/node/polyfills/internal/url.ts b/ext/node/polyfills/internal/url.ts new file mode 100644 index 000000000..415ad9be6 --- /dev/null +++ b/ext/node/polyfills/internal/url.ts @@ -0,0 +1,49 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { fileURLToPath } from "internal:deno_node/polyfills/url.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; + +const searchParams = Symbol("query"); + +export function toPathIfFileURL( + fileURLOrPath: string | Buffer | URL, +): string | Buffer { + if (!(fileURLOrPath instanceof URL)) { + return fileURLOrPath; + } + return fileURLToPath(fileURLOrPath); +} + +// Utility function that converts a URL object into an ordinary +// options object as expected by the http.request and https.request +// APIs. +// deno-lint-ignore no-explicit-any +export function urlToHttpOptions(url: any): any { + // deno-lint-ignore no-explicit-any + const options: any = { + protocol: url.protocol, + hostname: typeof url.hostname === "string" && + url.hostname.startsWith("[") + ? url.hostname.slice(1, -1) + : url.hostname, + hash: url.hash, + search: url.search, + pathname: url.pathname, + path: `${url.pathname || ""}${url.search || ""}`, + href: url.href, + }; + if (url.port !== "") { + options.port = Number(url.port); + } + if (url.username || url.password) { + options.auth = `${decodeURIComponent(url.username)}:${ + decodeURIComponent(url.password) + }`; + } + return options; +} + +export { searchParams as searchParamsSymbol }; + +export default { + toPathIfFileURL, +}; diff --git a/ext/node/polyfills/internal/util.mjs b/ext/node/polyfills/internal/util.mjs new file mode 100644 index 000000000..ba26c6a6a --- /dev/null +++ b/ext/node/polyfills/internal/util.mjs @@ -0,0 +1,141 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { validateFunction } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { normalizeEncoding, slowCases } from "internal:deno_node/polyfills/internal/normalize_encoding.mjs"; +export { normalizeEncoding, slowCases }; +import { ObjectCreate, StringPrototypeToUpperCase } from "internal:deno_node/polyfills/internal/primordials.mjs"; +import { ERR_UNKNOWN_SIGNAL } from "internal:deno_node/polyfills/internal/errors.ts"; +import { os } from "internal:deno_node/polyfills/internal_binding/constants.ts"; + +export const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom"); +export const kEnumerableProperty = Object.create(null); +kEnumerableProperty.enumerable = true; + +export const kEmptyObject = Object.freeze(Object.create(null)); + +export function once(callback) { + let called = false; + return function (...args) { + if (called) return; + called = true; + Reflect.apply(callback, this, args); + }; +} + +export function createDeferredPromise() { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + + return { promise, resolve, reject }; +} + +// In addition to being accessible through util.promisify.custom, +// this symbol is registered globally and can be accessed in any environment as +// Symbol.for('nodejs.util.promisify.custom'). +const kCustomPromisifiedSymbol = Symbol.for("nodejs.util.promisify.custom"); +// This is an internal Node symbol used by functions returning multiple +// arguments, e.g. ['bytesRead', 'buffer'] for fs.read(). +const kCustomPromisifyArgsSymbol = Symbol.for( + "nodejs.util.promisify.customArgs", +); + +export const customPromisifyArgs = kCustomPromisifyArgsSymbol; + +export function promisify( + original, +) { + validateFunction(original, "original"); + if (original[kCustomPromisifiedSymbol]) { + const fn = original[kCustomPromisifiedSymbol]; + + validateFunction(fn, "util.promisify.custom"); + + return Object.defineProperty(fn, kCustomPromisifiedSymbol, { + value: fn, + enumerable: false, + writable: false, + configurable: true, + }); + } + + // Names to create an object from in case the callback receives multiple + // arguments, e.g. ['bytesRead', 'buffer'] for fs.read. + const argumentNames = original[kCustomPromisifyArgsSymbol]; + function fn(...args) { + return new Promise((resolve, reject) => { + args.push((err, ...values) => { + if (err) { + return reject(err); + } + if (argumentNames !== undefined && values.length > 1) { + const obj = {}; + for (let i = 0; i < argumentNames.length; i++) { + obj[argumentNames[i]] = values[i]; + } + resolve(obj); + } else { + resolve(values[0]); + } + }); + Reflect.apply(original, this, args); + }); + } + + Object.setPrototypeOf(fn, Object.getPrototypeOf(original)); + + Object.defineProperty(fn, kCustomPromisifiedSymbol, { + value: fn, + enumerable: false, + writable: false, + configurable: true, + }); + return Object.defineProperties( + fn, + Object.getOwnPropertyDescriptors(original), + ); +} + +let signalsToNamesMapping; +function getSignalsToNamesMapping() { + if (signalsToNamesMapping !== undefined) { + return signalsToNamesMapping; + } + + signalsToNamesMapping = ObjectCreate(null); + for (const key in os.signals) { + signalsToNamesMapping[os.signals[key]] = key; + } + + return signalsToNamesMapping; +} + +export function convertToValidSignal(signal) { + if (typeof signal === "number" && getSignalsToNamesMapping()[signal]) { + return signal; + } + + if (typeof signal === "string") { + const signalName = os.signals[StringPrototypeToUpperCase(signal)]; + if (signalName) return signalName; + } + + throw new ERR_UNKNOWN_SIGNAL(signal); +} + +promisify.custom = kCustomPromisifiedSymbol; + +export default { + convertToValidSignal, + createDeferredPromise, + customInspectSymbol, + customPromisifyArgs, + kEmptyObject, + kEnumerableProperty, + normalizeEncoding, + once, + promisify, + slowCases, +}; diff --git a/ext/node/polyfills/internal/util/comparisons.ts b/ext/node/polyfills/internal/util/comparisons.ts new file mode 100644 index 000000000..1620e468b --- /dev/null +++ b/ext/node/polyfills/internal/util/comparisons.ts @@ -0,0 +1,681 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +// deno-lint-ignore-file +import { + isAnyArrayBuffer, + isArrayBufferView, + isBigIntObject, + isBooleanObject, + isBoxedPrimitive, + isDate, + isFloat32Array, + isFloat64Array, + isMap, + isNativeError, + isNumberObject, + isRegExp, + isSet, + isStringObject, + isSymbolObject, + isTypedArray, +} from "internal:deno_node/polyfills/internal/util/types.ts"; + +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + getOwnNonIndexProperties, + ONLY_ENUMERABLE, + SKIP_SYMBOLS, +} from "internal:deno_node/polyfills/internal_binding/util.ts"; + +enum valueType { + noIterator, + isArray, + isSet, + isMap, +} + +interface Memo { + val1: Map<unknown, unknown>; + val2: Map<unknown, unknown>; + position: number; +} +let memo: Memo; + +export function isDeepStrictEqual(val1: unknown, val2: unknown): boolean { + return innerDeepEqual(val1, val2, true); +} +export function isDeepEqual(val1: unknown, val2: unknown): boolean { + return innerDeepEqual(val1, val2, false); +} + +function innerDeepEqual( + val1: unknown, + val2: unknown, + strict: boolean, + memos = memo, +): boolean { + // Basic case covered by Strict Equality Comparison + if (val1 === val2) { + if (val1 !== 0) return true; + return strict ? Object.is(val1, val2) : true; + } + if (strict) { + // Cases where the values are not objects + // If both values are Not a Number NaN + if (typeof val1 !== "object") { + return ( + typeof val1 === "number" && Number.isNaN(val1) && Number.isNaN(val2) + ); + } + // If either value is null + if (typeof val2 !== "object" || val1 === null || val2 === null) { + return false; + } + // If the prototype are not the same + if (Object.getPrototypeOf(val1) !== Object.getPrototypeOf(val2)) { + return false; + } + } else { + // Non strict case where values are either null or NaN + if (val1 === null || typeof val1 !== "object") { + if (val2 === null || typeof val2 !== "object") { + return val1 == val2 || (Number.isNaN(val1) && Number.isNaN(val2)); + } + return false; + } + if (val2 === null || typeof val2 !== "object") { + return false; + } + } + + const val1Tag = Object.prototype.toString.call(val1); + const val2Tag = Object.prototype.toString.call(val2); + + // prototype must be Strictly Equal + if ( + val1Tag !== val2Tag + ) { + return false; + } + + // handling when values are array + if (Array.isArray(val1)) { + // quick rejection cases + if (!Array.isArray(val2) || val1.length !== val2.length) { + return false; + } + const filter = strict ? ONLY_ENUMERABLE : ONLY_ENUMERABLE | SKIP_SYMBOLS; + const keys1 = getOwnNonIndexProperties(val1, filter); + const keys2 = getOwnNonIndexProperties(val2, filter); + if (keys1.length !== keys2.length) { + return false; + } + return keyCheck(val1, val2, strict, memos, valueType.isArray, keys1); + } else if (val1Tag === "[object Object]") { + return keyCheck( + val1 as object, + val2 as object, + strict, + memos, + valueType.noIterator, + ); + } else if (val1 instanceof Date) { + if (!(val2 instanceof Date) || val1.getTime() !== val2.getTime()) { + return false; + } + } else if (val1 instanceof RegExp) { + if (!(val2 instanceof RegExp) || !areSimilarRegExps(val1, val2)) { + return false; + } + } else if (isNativeError(val1) || val1 instanceof Error) { + // stack may or may not be same, hence it shouldn't be compared + if ( + // How to handle the type errors here + (!isNativeError(val2) && !(val2 instanceof Error)) || + (val1 as Error).message !== (val2 as Error).message || + (val1 as Error).name !== (val2 as Error).name + ) { + return false; + } + } else if (isArrayBufferView(val1)) { + const TypedArrayPrototypeGetSymbolToStringTag = ( + val: + | BigInt64Array + | BigUint64Array + | Float32Array + | Float64Array + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint8ClampedArray + | Uint16Array + | Uint32Array, + ) => + Object.getOwnPropertySymbols(val) + .map((item) => item.toString()) + .toString(); + if ( + isTypedArray(val1) && + isTypedArray(val2) && + (TypedArrayPrototypeGetSymbolToStringTag(val1) !== + TypedArrayPrototypeGetSymbolToStringTag(val2)) + ) { + return false; + } + + if (!strict && (isFloat32Array(val1) || isFloat64Array(val1))) { + if (!areSimilarFloatArrays(val1, val2)) { + return false; + } + } else if (!areSimilarTypedArrays(val1, val2)) { + return false; + } + const filter = strict ? ONLY_ENUMERABLE : ONLY_ENUMERABLE | SKIP_SYMBOLS; + const keysVal1 = getOwnNonIndexProperties(val1 as object, filter); + const keysVal2 = getOwnNonIndexProperties(val2 as object, filter); + if (keysVal1.length !== keysVal2.length) { + return false; + } + return keyCheck( + val1 as object, + val2 as object, + strict, + memos, + valueType.noIterator, + keysVal1, + ); + } else if (isSet(val1)) { + if ( + !isSet(val2) || + (val1 as Set<unknown>).size !== (val2 as Set<unknown>).size + ) { + return false; + } + return keyCheck( + val1 as object, + val2 as object, + strict, + memos, + valueType.isSet, + ); + } else if (isMap(val1)) { + if ( + !isMap(val2) || val1.size !== val2.size + ) { + return false; + } + return keyCheck( + val1 as object, + val2 as object, + strict, + memos, + valueType.isMap, + ); + } else if (isAnyArrayBuffer(val1)) { + if (!isAnyArrayBuffer(val2) || !areEqualArrayBuffers(val1, val2)) { + return false; + } + } else if (isBoxedPrimitive(val1)) { + if (!isEqualBoxedPrimitive(val1, val2)) { + return false; + } + } else if ( + Array.isArray(val2) || + isArrayBufferView(val2) || + isSet(val2) || + isMap(val2) || + isDate(val2) || + isRegExp(val2) || + isAnyArrayBuffer(val2) || + isBoxedPrimitive(val2) || + isNativeError(val2) || + val2 instanceof Error + ) { + return false; + } + return keyCheck( + val1 as object, + val2 as object, + strict, + memos, + valueType.noIterator, + ); +} + +function keyCheck( + val1: object, + val2: object, + strict: boolean, + memos: Memo, + iterationType: valueType, + aKeys: (string | symbol)[] = [], +) { + if (arguments.length === 5) { + aKeys = Object.keys(val1); + const bKeys = Object.keys(val2); + + // The pair must have the same number of owned properties. + if (aKeys.length !== bKeys.length) { + return false; + } + } + + // Cheap key test + let i = 0; + for (; i < aKeys.length; i++) { + if (!val2.propertyIsEnumerable(aKeys[i])) { + return false; + } + } + + if (strict && arguments.length === 5) { + const symbolKeysA = Object.getOwnPropertySymbols(val1); + if (symbolKeysA.length !== 0) { + let count = 0; + for (i = 0; i < symbolKeysA.length; i++) { + const key = symbolKeysA[i]; + if (val1.propertyIsEnumerable(key)) { + if (!val2.propertyIsEnumerable(key)) { + return false; + } + // added toString here + aKeys.push(key.toString()); + count++; + } else if (val2.propertyIsEnumerable(key)) { + return false; + } + } + const symbolKeysB = Object.getOwnPropertySymbols(val2); + if ( + symbolKeysA.length !== symbolKeysB.length && + getEnumerables(val2, symbolKeysB).length !== count + ) { + return false; + } + } else { + const symbolKeysB = Object.getOwnPropertySymbols(val2); + if ( + symbolKeysB.length !== 0 && + getEnumerables(val2, symbolKeysB).length !== 0 + ) { + return false; + } + } + } + if ( + aKeys.length === 0 && + (iterationType === valueType.noIterator || + (iterationType === valueType.isArray && (val1 as []).length === 0) || + (val1 as Set<unknown>).size === 0) + ) { + return true; + } + + if (memos === undefined) { + memos = { + val1: new Map(), + val2: new Map(), + position: 0, + }; + } else { + const val2MemoA = memos.val1.get(val1); + if (val2MemoA !== undefined) { + const val2MemoB = memos.val2.get(val2); + if (val2MemoB !== undefined) { + return val2MemoA === val2MemoB; + } + } + memos.position++; + } + + memos.val1.set(val1, memos.position); + memos.val2.set(val2, memos.position); + + const areEq = objEquiv(val1, val2, strict, aKeys, memos, iterationType); + + memos.val1.delete(val1); + memos.val2.delete(val2); + + return areEq; +} + +function areSimilarRegExps(a: RegExp, b: RegExp) { + return a.source === b.source && a.flags === b.flags && + a.lastIndex === b.lastIndex; +} + +// TODO(standvpmnt): add type for arguments +function areSimilarFloatArrays(arr1: any, arr2: any): boolean { + if (arr1.byteLength !== arr2.byteLength) { + return false; + } + for (let i = 0; i < arr1.byteLength; i++) { + if (arr1[i] !== arr2[i]) { + return false; + } + } + return true; +} + +// TODO(standvpmnt): add type for arguments +function areSimilarTypedArrays(arr1: any, arr2: any): boolean { + if (arr1.byteLength !== arr2.byteLength) { + return false; + } + return ( + Buffer.compare( + new Uint8Array(arr1.buffer, arr1.byteOffset, arr1.byteLength), + new Uint8Array(arr2.buffer, arr2.byteOffset, arr2.byteLength), + ) === 0 + ); +} +// TODO(standvpmnt): add type for arguments +function areEqualArrayBuffers(buf1: any, buf2: any): boolean { + return ( + buf1.byteLength === buf2.byteLength && + Buffer.compare(new Uint8Array(buf1), new Uint8Array(buf2)) === 0 + ); +} + +// TODO(standvpmnt): this check of getOwnPropertySymbols and getOwnPropertyNames +// length is sufficient to handle the current test case, however this will fail +// to catch a scenario wherein the getOwnPropertySymbols and getOwnPropertyNames +// length is the same(will be very contrived but a possible shortcoming +function isEqualBoxedPrimitive(a: any, b: any): boolean { + if ( + Object.getOwnPropertyNames(a).length !== + Object.getOwnPropertyNames(b).length + ) { + return false; + } + if ( + Object.getOwnPropertySymbols(a).length !== + Object.getOwnPropertySymbols(b).length + ) { + return false; + } + if (isNumberObject(a)) { + return ( + isNumberObject(b) && + Object.is( + Number.prototype.valueOf.call(a), + Number.prototype.valueOf.call(b), + ) + ); + } + if (isStringObject(a)) { + return ( + isStringObject(b) && + (String.prototype.valueOf.call(a) === String.prototype.valueOf.call(b)) + ); + } + if (isBooleanObject(a)) { + return ( + isBooleanObject(b) && + (Boolean.prototype.valueOf.call(a) === Boolean.prototype.valueOf.call(b)) + ); + } + if (isBigIntObject(a)) { + return ( + isBigIntObject(b) && + (BigInt.prototype.valueOf.call(a) === BigInt.prototype.valueOf.call(b)) + ); + } + if (isSymbolObject(a)) { + return ( + isSymbolObject(b) && + (Symbol.prototype.valueOf.call(a) === + Symbol.prototype.valueOf.call(b)) + ); + } + // assert.fail(`Unknown boxed type ${val1}`); + // return false; + throw Error(`Unknown boxed type`); +} + +function getEnumerables(val: any, keys: any) { + return keys.filter((key: string) => val.propertyIsEnumerable(key)); +} + +function objEquiv( + obj1: any, + obj2: any, + strict: boolean, + keys: any, + memos: Memo, + iterationType: valueType, +): boolean { + let i = 0; + + if (iterationType === valueType.isSet) { + if (!setEquiv(obj1, obj2, strict, memos)) { + return false; + } + } else if (iterationType === valueType.isMap) { + if (!mapEquiv(obj1, obj2, strict, memos)) { + return false; + } + } else if (iterationType === valueType.isArray) { + for (; i < obj1.length; i++) { + if (obj1.hasOwnProperty(i)) { + if ( + !obj2.hasOwnProperty(i) || + !innerDeepEqual(obj1[i], obj2[i], strict, memos) + ) { + return false; + } + } else if (obj2.hasOwnProperty(i)) { + return false; + } else { + const keys1 = Object.keys(obj1); + for (; i < keys1.length; i++) { + const key = keys1[i]; + if ( + !obj2.hasOwnProperty(key) || + !innerDeepEqual(obj1[key], obj2[key], strict, memos) + ) { + return false; + } + } + if (keys1.length !== Object.keys(obj2).length) { + return false; + } + if (keys1.length !== Object.keys(obj2).length) { + return false; + } + return true; + } + } + } + + // Expensive test + for (i = 0; i < keys.length; i++) { + const key = keys[i]; + if (!innerDeepEqual(obj1[key], obj2[key], strict, memos)) { + return false; + } + } + return true; +} + +function findLooseMatchingPrimitives( + primitive: unknown, +): boolean | null | undefined { + switch (typeof primitive) { + case "undefined": + return null; + case "object": + return undefined; + case "symbol": + return false; + case "string": + primitive = +primitive; + case "number": + if (Number.isNaN(primitive)) { + return false; + } + } + return true; +} + +function setMightHaveLoosePrim( + set1: Set<unknown>, + set2: Set<unknown>, + primitive: any, +) { + const altValue = findLooseMatchingPrimitives(primitive); + if (altValue != null) return altValue; + + return set2.has(altValue) && !set1.has(altValue); +} + +function setHasEqualElement( + set: any, + val1: any, + strict: boolean, + memos: Memo, +): boolean { + for (const val2 of set) { + if (innerDeepEqual(val1, val2, strict, memos)) { + set.delete(val2); + return true; + } + } + + return false; +} + +function setEquiv(set1: any, set2: any, strict: boolean, memos: Memo): boolean { + let set = null; + for (const item of set1) { + if (typeof item === "object" && item !== null) { + if (set === null) { + // What is SafeSet from primordials? + // set = new SafeSet(); + set = new Set(); + } + set.add(item); + } else if (!set2.has(item)) { + if (strict) return false; + + if (!setMightHaveLoosePrim(set1, set2, item)) { + return false; + } + + if (set === null) { + set = new Set(); + } + set.add(item); + } + } + + if (set !== null) { + for (const item of set2) { + if (typeof item === "object" && item !== null) { + if (!setHasEqualElement(set, item, strict, memos)) return false; + } else if ( + !strict && + !set1.has(item) && + !setHasEqualElement(set, item, strict, memos) + ) { + return false; + } + } + return set.size === 0; + } + + return true; +} + +// TODO(standvpmnt): add types for argument +function mapMightHaveLoosePrimitive( + map1: Map<unknown, unknown>, + map2: Map<unknown, unknown>, + primitive: any, + item: any, + memos: Memo, +): boolean { + const altValue = findLooseMatchingPrimitives(primitive); + if (altValue != null) { + return altValue; + } + const curB = map2.get(altValue); + if ( + (curB === undefined && !map2.has(altValue)) || + !innerDeepEqual(item, curB, false, memo) + ) { + return false; + } + return !map1.has(altValue) && innerDeepEqual(item, curB, false, memos); +} + +function mapEquiv(map1: any, map2: any, strict: boolean, memos: Memo): boolean { + let set = null; + + for (const { 0: key, 1: item1 } of map1) { + if (typeof key === "object" && key !== null) { + if (set === null) { + set = new Set(); + } + set.add(key); + } else { + const item2 = map2.get(key); + if ( + ( + (item2 === undefined && !map2.has(key)) || + !innerDeepEqual(item1, item2, strict, memos) + ) + ) { + if (strict) return false; + if (!mapMightHaveLoosePrimitive(map1, map2, key, item1, memos)) { + return false; + } + if (set === null) { + set = new Set(); + } + set.add(key); + } + } + } + + if (set !== null) { + for (const { 0: key, 1: item } of map2) { + if (typeof key === "object" && key !== null) { + if (!mapHasEqualEntry(set, map1, key, item, strict, memos)) { + return false; + } + } else if ( + !strict && (!map1.has(key) || + !innerDeepEqual(map1.get(key), item, false, memos)) && + !mapHasEqualEntry(set, map1, key, item, false, memos) + ) { + return false; + } + } + return set.size === 0; + } + + return true; +} + +function mapHasEqualEntry( + set: any, + map: any, + key1: any, + item1: any, + strict: boolean, + memos: Memo, +): boolean { + for (const key2 of set) { + if ( + innerDeepEqual(key1, key2, strict, memos) && + innerDeepEqual(item1, map.get(key2), strict, memos) + ) { + set.delete(key2); + return true; + } + } + return false; +} diff --git a/ext/node/polyfills/internal/util/debuglog.ts b/ext/node/polyfills/internal/util/debuglog.ts new file mode 100644 index 000000000..498facbd1 --- /dev/null +++ b/ext/node/polyfills/internal/util/debuglog.ts @@ -0,0 +1,121 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +import { inspect } from "internal:deno_node/polyfills/internal/util/inspect.mjs"; + +// `debugImpls` and `testEnabled` are deliberately not initialized so any call +// to `debuglog()` before `initializeDebugEnv()` is called will throw. +let debugImpls: Record<string, (...args: unknown[]) => void>; +let testEnabled: (str: string) => boolean; + +// `debugEnv` is initial value of process.env.NODE_DEBUG +function initializeDebugEnv(debugEnv: string) { + debugImpls = Object.create(null); + if (debugEnv) { + // This is run before any user code, it's OK not to use primordials. + debugEnv = debugEnv.replace(/[|\\{}()[\]^$+?.]/g, "\\$&") + .replaceAll("*", ".*") + .replaceAll(",", "$|^"); + const debugEnvRegex = new RegExp(`^${debugEnv}$`, "i"); + testEnabled = (str) => debugEnvRegex.exec(str) !== null; + } else { + testEnabled = () => false; + } +} + +// Emits warning when user sets +// NODE_DEBUG=http or NODE_DEBUG=http2. +function emitWarningIfNeeded(set: string) { + if ("HTTP" === set || "HTTP2" === set) { + console.warn( + "Setting the NODE_DEBUG environment variable " + + "to '" + set.toLowerCase() + "' can expose sensitive " + + "data (such as passwords, tokens and authentication headers) " + + "in the resulting log.", + ); + } +} + +const noop = () => {}; + +function debuglogImpl( + enabled: boolean, + set: string, +): (...args: unknown[]) => void { + if (debugImpls[set] === undefined) { + if (enabled) { + emitWarningIfNeeded(set); + debugImpls[set] = function debug(...args: unknown[]) { + const msg = args.map((arg) => inspect(arg)).join(" "); + console.error("%s %s: %s", set, String(Deno.pid), msg); + }; + } else { + debugImpls[set] = noop; + } + } + + return debugImpls[set]; +} + +// debuglogImpl depends on process.pid and process.env.NODE_DEBUG, +// so it needs to be called lazily in top scopes of internal modules +// that may be loaded before these run time states are allowed to +// be accessed. +export function debuglog( + set: string, + cb?: (debug: (...args: unknown[]) => void) => void, +) { + function init() { + set = set.toUpperCase(); + enabled = testEnabled(set); + } + + let debug = (...args: unknown[]): void => { + init(); + // Only invokes debuglogImpl() when the debug function is + // called for the first time. + debug = debuglogImpl(enabled, set); + + if (typeof cb === "function") { + cb(debug); + } + + return debug(...args); + }; + + let enabled: boolean; + let test = () => { + init(); + test = () => enabled; + return enabled; + }; + + const logger = (...args: unknown[]) => debug(...args); + + Object.defineProperty(logger, "enabled", { + get() { + return test(); + }, + configurable: true, + enumerable: true, + }); + + return logger; +} + +let debugEnv; +/* TODO(kt3k): enable initializing debugEnv. +It's not possible to access env var when snapshotting. + +try { + debugEnv = Deno.env.get("NODE_DEBUG") ?? ""; +} catch (error) { + if (error instanceof Deno.errors.PermissionDenied) { + debugEnv = ""; + } else { + throw error; + } +} +*/ +initializeDebugEnv(debugEnv); + +export default { debuglog }; diff --git a/ext/node/polyfills/internal/util/inspect.mjs b/ext/node/polyfills/internal/util/inspect.mjs new file mode 100644 index 000000000..53a878aa3 --- /dev/null +++ b/ext/node/polyfills/internal/util/inspect.mjs @@ -0,0 +1,2237 @@ +// Copyright 2018-2023 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 * as types from "internal:deno_node/polyfills/internal/util/types.ts"; +import { validateObject, validateString } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { codes } from "internal:deno_node/polyfills/internal/error_codes.ts"; + +import { + ALL_PROPERTIES, + getOwnNonIndexProperties, + ONLY_ENUMERABLE, +} from "internal:deno_node/polyfills/internal_binding/util.ts"; + +const kObjectType = 0; +const kArrayType = 1; +const kArrayExtrasType = 2; + +const kMinLineLength = 16; + +// Constants to map the iterator state. +const kWeak = 0; +const kIterator = 1; +const kMapEntries = 2; + +const kPending = 0; +const kRejected = 2; + +// Escaped control characters (plus the single quote and the backslash). Use +// empty strings to fill up unused entries. +// deno-fmt-ignore +const meta = [ + '\\x00', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\x07', // x07 + '\\b', '\\t', '\\n', '\\x0B', '\\f', '\\r', '\\x0E', '\\x0F', // x0F + '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', // x17 + '\\x18', '\\x19', '\\x1A', '\\x1B', '\\x1C', '\\x1D', '\\x1E', '\\x1F', // x1F + '', '', '', '', '', '', '', "\\'", '', '', '', '', '', '', '', '', // x2F + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', // x3F + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', // x4F + '', '', '', '', '', '', '', '', '', '', '', '', '\\\\', '', '', '', // x5F + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', // x6F + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '\\x7F', // x7F + '\\x80', '\\x81', '\\x82', '\\x83', '\\x84', '\\x85', '\\x86', '\\x87', // x87 + '\\x88', '\\x89', '\\x8A', '\\x8B', '\\x8C', '\\x8D', '\\x8E', '\\x8F', // x8F + '\\x90', '\\x91', '\\x92', '\\x93', '\\x94', '\\x95', '\\x96', '\\x97', // x97 + '\\x98', '\\x99', '\\x9A', '\\x9B', '\\x9C', '\\x9D', '\\x9E', '\\x9F', // x9F +]; + +// https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot +const isUndetectableObject = (v) => typeof v === "undefined" && v !== undefined; + +// deno-lint-ignore no-control-regex +const strEscapeSequencesRegExp = /[\x00-\x1f\x27\x5c\x7f-\x9f]/; +// deno-lint-ignore no-control-regex +const strEscapeSequencesReplacer = /[\x00-\x1f\x27\x5c\x7f-\x9f]/g; +// deno-lint-ignore no-control-regex +const strEscapeSequencesRegExpSingle = /[\x00-\x1f\x5c\x7f-\x9f]/; +// deno-lint-ignore no-control-regex +const strEscapeSequencesReplacerSingle = /[\x00-\x1f\x5c\x7f-\x9f]/g; + +const keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/; +const numberRegExp = /^(0|[1-9][0-9]*)$/; +const nodeModulesRegExp = /[/\\]node_modules[/\\](.+?)(?=[/\\])/g; + +const classRegExp = /^(\s+[^(]*?)\s*{/; +// eslint-disable-next-line node-core/no-unescaped-regexp-dot +const stripCommentsRegExp = /(\/\/.*?\n)|(\/\*(.|\n)*?\*\/)/g; + +const inspectDefaultOptions = { + showHidden: false, + depth: 2, + colors: false, + customInspect: true, + showProxy: false, + maxArrayLength: 100, + maxStringLength: 10000, + breakLength: 80, + compact: 3, + sorted: false, + getters: false, +}; + +function getUserOptions(ctx, isCrossContext) { + const ret = { + stylize: ctx.stylize, + showHidden: ctx.showHidden, + depth: ctx.depth, + colors: ctx.colors, + customInspect: ctx.customInspect, + showProxy: ctx.showProxy, + maxArrayLength: ctx.maxArrayLength, + maxStringLength: ctx.maxStringLength, + breakLength: ctx.breakLength, + compact: ctx.compact, + sorted: ctx.sorted, + getters: ctx.getters, + ...ctx.userOptions, + }; + + // Typically, the target value will be an instance of `Object`. If that is + // *not* the case, the object may come from another vm.Context, and we want + // to avoid passing it objects from this Context in that case, so we remove + // the prototype from the returned object itself + the `stylize()` function, + // and remove all other non-primitives, including non-primitive user options. + if (isCrossContext) { + Object.setPrototypeOf(ret, null); + for (const key of Object.keys(ret)) { + if ( + (typeof ret[key] === "object" || typeof ret[key] === "function") && + ret[key] !== null + ) { + delete ret[key]; + } + } + ret.stylize = Object.setPrototypeOf((value, flavour) => { + let stylized; + try { + stylized = `${ctx.stylize(value, flavour)}`; + } catch { + // noop + } + + if (typeof stylized !== "string") return value; + // `stylized` is a string as it should be, which is safe to pass along. + return stylized; + }, null); + } + + return ret; +} + +/** + * Echos the value of any input. Tries to print the value out + * in the best way possible given the different types. + */ +/* Legacy: value, showHidden, depth, colors */ +export function inspect(value, opts) { + // Default options + const ctx = { + budget: {}, + indentationLvl: 0, + seen: [], + currentDepth: 0, + stylize: stylizeNoColor, + showHidden: inspectDefaultOptions.showHidden, + depth: inspectDefaultOptions.depth, + colors: inspectDefaultOptions.colors, + customInspect: inspectDefaultOptions.customInspect, + showProxy: inspectDefaultOptions.showProxy, + maxArrayLength: inspectDefaultOptions.maxArrayLength, + maxStringLength: inspectDefaultOptions.maxStringLength, + breakLength: inspectDefaultOptions.breakLength, + compact: inspectDefaultOptions.compact, + sorted: inspectDefaultOptions.sorted, + getters: inspectDefaultOptions.getters, + }; + if (arguments.length > 1) { + // Legacy... + if (arguments.length > 2) { + if (arguments[2] !== undefined) { + ctx.depth = arguments[2]; + } + if (arguments.length > 3 && arguments[3] !== undefined) { + ctx.colors = arguments[3]; + } + } + // Set user-specified options + if (typeof opts === "boolean") { + ctx.showHidden = opts; + } else if (opts) { + const optKeys = Object.keys(opts); + for (let i = 0; i < optKeys.length; ++i) { + const key = optKeys[i]; + // TODO(BridgeAR): Find a solution what to do about stylize. Either make + // this function public or add a new API with a similar or better + // functionality. + if ( + // deno-lint-ignore no-prototype-builtins + inspectDefaultOptions.hasOwnProperty(key) || + key === "stylize" + ) { + ctx[key] = opts[key]; + } else if (ctx.userOptions === undefined) { + // This is required to pass through the actual user input. + ctx.userOptions = opts; + } + } + } + } + if (ctx.colors) ctx.stylize = stylizeWithColor; + if (ctx.maxArrayLength === null) ctx.maxArrayLength = Infinity; + if (ctx.maxStringLength === null) ctx.maxStringLength = Infinity; + return formatValue(ctx, value, 0); +} +const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom"); +inspect.custom = customInspectSymbol; + +Object.defineProperty(inspect, "defaultOptions", { + get() { + return inspectDefaultOptions; + }, + set(options) { + validateObject(options, "options"); + return Object.assign(inspectDefaultOptions, options); + }, +}); + +// Set Graphics Rendition https://en.wikipedia.org/wiki/ANSI_escape_code#graphics +// Each color consists of an array with the color code as first entry and the +// reset code as second entry. +const defaultFG = 39; +const defaultBG = 49; +inspect.colors = Object.assign(Object.create(null), { + reset: [0, 0], + bold: [1, 22], + dim: [2, 22], // Alias: faint + italic: [3, 23], + underline: [4, 24], + blink: [5, 25], + // Swap foreground and background colors + inverse: [7, 27], // Alias: swapcolors, swapColors + hidden: [8, 28], // Alias: conceal + strikethrough: [9, 29], // Alias: strikeThrough, crossedout, crossedOut + doubleunderline: [21, 24], // Alias: doubleUnderline + black: [30, defaultFG], + red: [31, defaultFG], + green: [32, defaultFG], + yellow: [33, defaultFG], + blue: [34, defaultFG], + magenta: [35, defaultFG], + cyan: [36, defaultFG], + white: [37, defaultFG], + bgBlack: [40, defaultBG], + bgRed: [41, defaultBG], + bgGreen: [42, defaultBG], + bgYellow: [43, defaultBG], + bgBlue: [44, defaultBG], + bgMagenta: [45, defaultBG], + bgCyan: [46, defaultBG], + bgWhite: [47, defaultBG], + framed: [51, 54], + overlined: [53, 55], + gray: [90, defaultFG], // Alias: grey, blackBright + redBright: [91, defaultFG], + greenBright: [92, defaultFG], + yellowBright: [93, defaultFG], + blueBright: [94, defaultFG], + magentaBright: [95, defaultFG], + cyanBright: [96, defaultFG], + whiteBright: [97, defaultFG], + bgGray: [100, defaultBG], // Alias: bgGrey, bgBlackBright + bgRedBright: [101, defaultBG], + bgGreenBright: [102, defaultBG], + bgYellowBright: [103, defaultBG], + bgBlueBright: [104, defaultBG], + bgMagentaBright: [105, defaultBG], + bgCyanBright: [106, defaultBG], + bgWhiteBright: [107, defaultBG], +}); + +function defineColorAlias(target, alias) { + Object.defineProperty(inspect.colors, alias, { + get() { + return this[target]; + }, + set(value) { + this[target] = value; + }, + configurable: true, + enumerable: false, + }); +} + +defineColorAlias("gray", "grey"); +defineColorAlias("gray", "blackBright"); +defineColorAlias("bgGray", "bgGrey"); +defineColorAlias("bgGray", "bgBlackBright"); +defineColorAlias("dim", "faint"); +defineColorAlias("strikethrough", "crossedout"); +defineColorAlias("strikethrough", "strikeThrough"); +defineColorAlias("strikethrough", "crossedOut"); +defineColorAlias("hidden", "conceal"); +defineColorAlias("inverse", "swapColors"); +defineColorAlias("inverse", "swapcolors"); +defineColorAlias("doubleunderline", "doubleUnderline"); + +// TODO(BridgeAR): Add function style support for more complex styles. +// Don't use 'blue' not visible on cmd.exe +inspect.styles = Object.assign(Object.create(null), { + special: "cyan", + number: "yellow", + bigint: "yellow", + boolean: "yellow", + undefined: "grey", + null: "bold", + string: "green", + symbol: "green", + date: "magenta", + // "name": intentionally not styling + // TODO(BridgeAR): Highlight regular expressions properly. + regexp: "red", + module: "underline", +}); + +function addQuotes(str, quotes) { + if (quotes === -1) { + return `"${str}"`; + } + if (quotes === -2) { + return `\`${str}\``; + } + return `'${str}'`; +} + +// TODO(wafuwafu13): Figure out +const escapeFn = (str) => meta[str.charCodeAt(0)]; + +// Escape control characters, single quotes and the backslash. +// This is similar to JSON stringify escaping. +function strEscape(str) { + let escapeTest = strEscapeSequencesRegExp; + let escapeReplace = strEscapeSequencesReplacer; + let singleQuote = 39; + + // Check for double quotes. If not present, do not escape single quotes and + // instead wrap the text in double quotes. If double quotes exist, check for + // backticks. If they do not exist, use those as fallback instead of the + // double quotes. + if (str.includes("'")) { + // This invalidates the charCode and therefore can not be matched for + // anymore. + if (!str.includes('"')) { + singleQuote = -1; + } else if ( + !str.includes("`") && + !str.includes("${") + ) { + singleQuote = -2; + } + if (singleQuote !== 39) { + escapeTest = strEscapeSequencesRegExpSingle; + escapeReplace = strEscapeSequencesReplacerSingle; + } + } + + // Some magic numbers that worked out fine while benchmarking with v8 6.0 + if (str.length < 5000 && !escapeTest.test(str)) { + return addQuotes(str, singleQuote); + } + if (str.length > 100) { + str = str.replace(escapeReplace, escapeFn); + return addQuotes(str, singleQuote); + } + + let result = ""; + let last = 0; + const lastIndex = str.length; + for (let i = 0; i < lastIndex; i++) { + const point = str.charCodeAt(i); + if ( + point === singleQuote || + point === 92 || + point < 32 || + (point > 126 && point < 160) + ) { + if (last === i) { + result += meta[point]; + } else { + result += `${str.slice(last, i)}${meta[point]}`; + } + last = i + 1; + } + } + + if (last !== lastIndex) { + result += str.slice(last); + } + return addQuotes(result, singleQuote); +} + +function stylizeWithColor(str, styleType) { + const style = inspect.styles[styleType]; + if (style !== undefined) { + const color = inspect.colors[style]; + if (color !== undefined) { + return `\u001b[${color[0]}m${str}\u001b[${color[1]}m`; + } + } + return str; +} + +function stylizeNoColor(str) { + return str; +} + +// Note: using `formatValue` directly requires the indentation level to be +// corrected by setting `ctx.indentationLvL += diff` and then to decrease the +// value afterwards again. +function formatValue( + ctx, + value, + recurseTimes, + typedArray, +) { + // Primitive types cannot have properties. + if ( + typeof value !== "object" && + typeof value !== "function" && + !isUndetectableObject(value) + ) { + return formatPrimitive(ctx.stylize, value, ctx); + } + if (value === null) { + return ctx.stylize("null", "null"); + } + + // Memorize the context for custom inspection on proxies. + const context = value; + // Always check for proxies to prevent side effects and to prevent triggering + // any proxy handlers. + // TODO(wafuwafu13): Set Proxy + const proxy = undefined; + // const proxy = getProxyDetails(value, !!ctx.showProxy); + // if (proxy !== undefined) { + // if (ctx.showProxy) { + // return formatProxy(ctx, proxy, recurseTimes); + // } + // value = proxy; + // } + + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it. + if (ctx.customInspect) { + const maybeCustom = value[customInspectSymbol]; + if ( + typeof maybeCustom === "function" && + // Filter out the util module, its inspect function is special. + maybeCustom !== inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value) + ) { + // This makes sure the recurseTimes are reported as before while using + // a counter internally. + const depth = ctx.depth === null ? null : ctx.depth - recurseTimes; + const isCrossContext = proxy !== undefined || + !(context instanceof Object); + const ret = maybeCustom.call( + context, + depth, + getUserOptions(ctx, isCrossContext), + ); + // If the custom inspection method returned `this`, don't go into + // infinite recursion. + if (ret !== context) { + if (typeof ret !== "string") { + return formatValue(ctx, ret, recurseTimes); + } + return ret.replace(/\n/g, `\n${" ".repeat(ctx.indentationLvl)}`); + } + } + } + + // Using an array here is actually better for the average case than using + // a Set. `seen` will only check for the depth and will never grow too large. + if (ctx.seen.includes(value)) { + let index = 1; + if (ctx.circular === undefined) { + ctx.circular = new Map(); + ctx.circular.set(value, index); + } else { + index = ctx.circular.get(value); + if (index === undefined) { + index = ctx.circular.size + 1; + ctx.circular.set(value, index); + } + } + return ctx.stylize(`[Circular *${index}]`, "special"); + } + + return formatRaw(ctx, value, recurseTimes, typedArray); +} + +function formatRaw(ctx, value, recurseTimes, typedArray) { + let keys; + let protoProps; + if (ctx.showHidden && (recurseTimes <= ctx.depth || ctx.depth === null)) { + protoProps = []; + } + + const constructor = getConstructorName(value, ctx, recurseTimes, protoProps); + // Reset the variable to check for this later on. + if (protoProps !== undefined && protoProps.length === 0) { + protoProps = undefined; + } + + let tag = value[Symbol.toStringTag]; + // Only list the tag in case it's non-enumerable / not an own property. + // Otherwise we'd print this twice. + if ( + typeof tag !== "string" + // TODO(wafuwafu13): Implement + // (tag !== "" && + // (ctx.showHidden + // ? Object.prototype.hasOwnProperty + // : Object.prototype.propertyIsEnumerable)( + // value, + // Symbol.toStringTag, + // )) + ) { + tag = ""; + } + let base = ""; + let formatter = getEmptyFormatArray; + let braces; + let noIterator = true; + let i = 0; + const filter = ctx.showHidden ? ALL_PROPERTIES : ONLY_ENUMERABLE; + + let extrasType = kObjectType; + + // Iterators and the rest are split to reduce checks. + // We have to check all values in case the constructor is set to null. + // Otherwise it would not possible to identify all types properly. + if (value[Symbol.iterator] || constructor === null) { + noIterator = false; + if (Array.isArray(value)) { + // Only set the constructor for non ordinary ("Array [...]") arrays. + const prefix = (constructor !== "Array" || tag !== "") + ? getPrefix(constructor, tag, "Array", `(${value.length})`) + : ""; + keys = getOwnNonIndexProperties(value, filter); + braces = [`${prefix}[`, "]"]; + if (value.length === 0 && keys.length === 0 && protoProps === undefined) { + return `${braces[0]}]`; + } + extrasType = kArrayExtrasType; + formatter = formatArray; + } else if (types.isSet(value)) { + const size = value.size; + const prefix = getPrefix(constructor, tag, "Set", `(${size})`); + keys = getKeys(value, ctx.showHidden); + formatter = constructor !== null + ? formatSet.bind(null, value) + : formatSet.bind(null, value.values()); + if (size === 0 && keys.length === 0 && protoProps === undefined) { + return `${prefix}{}`; + } + braces = [`${prefix}{`, "}"]; + } else if (types.isMap(value)) { + const size = value.size; + const prefix = getPrefix(constructor, tag, "Map", `(${size})`); + keys = getKeys(value, ctx.showHidden); + formatter = constructor !== null + ? formatMap.bind(null, value) + : formatMap.bind(null, value.entries()); + if (size === 0 && keys.length === 0 && protoProps === undefined) { + return `${prefix}{}`; + } + braces = [`${prefix}{`, "}"]; + } else if (types.isTypedArray(value)) { + keys = getOwnNonIndexProperties(value, filter); + const bound = value; + const fallback = ""; + if (constructor === null) { + // TODO(wafuwafu13): Implement + // fallback = TypedArrayPrototypeGetSymbolToStringTag(value); + // // Reconstruct the array information. + // bound = new primordials[fallback](value); + } + const size = value.length; + const prefix = getPrefix(constructor, tag, fallback, `(${size})`); + braces = [`${prefix}[`, "]"]; + if (value.length === 0 && keys.length === 0 && !ctx.showHidden) { + return `${braces[0]}]`; + } + // Special handle the value. The original value is required below. The + // bound function is required to reconstruct missing information. + (formatter) = formatTypedArray.bind(null, bound, size); + extrasType = kArrayExtrasType; + } else if (types.isMapIterator(value)) { + keys = getKeys(value, ctx.showHidden); + braces = getIteratorBraces("Map", tag); + // Add braces to the formatter parameters. + (formatter) = formatIterator.bind(null, braces); + } else if (types.isSetIterator(value)) { + keys = getKeys(value, ctx.showHidden); + braces = getIteratorBraces("Set", tag); + // Add braces to the formatter parameters. + (formatter) = formatIterator.bind(null, braces); + } else { + noIterator = true; + } + } + if (noIterator) { + keys = getKeys(value, ctx.showHidden); + braces = ["{", "}"]; + if (constructor === "Object") { + if (types.isArgumentsObject(value)) { + braces[0] = "[Arguments] {"; + } else if (tag !== "") { + braces[0] = `${getPrefix(constructor, tag, "Object")}{`; + } + if (keys.length === 0 && protoProps === undefined) { + return `${braces[0]}}`; + } + } else if (typeof value === "function") { + base = getFunctionBase(value, constructor, tag); + if (keys.length === 0 && protoProps === undefined) { + return ctx.stylize(base, "special"); + } + } else if (types.isRegExp(value)) { + // Make RegExps say that they are RegExps + base = RegExp(constructor !== null ? value : new RegExp(value)) + .toString(); + const prefix = getPrefix(constructor, tag, "RegExp"); + if (prefix !== "RegExp ") { + base = `${prefix}${base}`; + } + if ( + (keys.length === 0 && protoProps === undefined) || + (recurseTimes > ctx.depth && ctx.depth !== null) + ) { + return ctx.stylize(base, "regexp"); + } + } else if (types.isDate(value)) { + // Make dates with properties first say the date + base = Number.isNaN(value.getTime()) + ? value.toString() + : value.toISOString(); + const prefix = getPrefix(constructor, tag, "Date"); + if (prefix !== "Date ") { + base = `${prefix}${base}`; + } + if (keys.length === 0 && protoProps === undefined) { + return ctx.stylize(base, "date"); + } + } else if (value instanceof Error) { + base = formatError(value, constructor, tag, ctx, keys); + if (keys.length === 0 && protoProps === undefined) { + return base; + } + } else if (types.isAnyArrayBuffer(value)) { + // Fast path for ArrayBuffer and SharedArrayBuffer. + // Can't do the same for DataView because it has a non-primitive + // .buffer property that we need to recurse for. + const arrayType = types.isArrayBuffer(value) + ? "ArrayBuffer" + : "SharedArrayBuffer"; + const prefix = getPrefix(constructor, tag, arrayType); + if (typedArray === undefined) { + (formatter) = formatArrayBuffer; + } else if (keys.length === 0 && protoProps === undefined) { + return prefix + + `{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`; + } + braces[0] = `${prefix}{`; + Array.prototype.unshift.call(keys, "byteLength"); + } else if (types.isDataView(value)) { + braces[0] = `${getPrefix(constructor, tag, "DataView")}{`; + // .buffer goes last, it's not a primitive like the others. + Array.prototype.unshift.call(keys, "byteLength", "byteOffset", "buffer"); + } else if (types.isPromise(value)) { + braces[0] = `${getPrefix(constructor, tag, "Promise")}{`; + (formatter) = formatPromise; + } else if (types.isWeakSet(value)) { + braces[0] = `${getPrefix(constructor, tag, "WeakSet")}{`; + (formatter) = ctx.showHidden ? formatWeakSet : formatWeakCollection; + } else if (types.isWeakMap(value)) { + braces[0] = `${getPrefix(constructor, tag, "WeakMap")}{`; + (formatter) = ctx.showHidden ? formatWeakMap : formatWeakCollection; + } else if (types.isModuleNamespaceObject(value)) { + braces[0] = `${getPrefix(constructor, tag, "Module")}{`; + // Special handle keys for namespace objects. + (formatter) = formatNamespaceObject.bind(null, keys); + } else if (types.isBoxedPrimitive(value)) { + base = getBoxedBase(value, ctx, keys, constructor, tag); + if (keys.length === 0 && protoProps === undefined) { + return base; + } + } else { + if (keys.length === 0 && protoProps === undefined) { + // TODO(wafuwafu13): Implement + // if (types.isExternal(value)) { + // const address = getExternalValue(value).toString(16); + // return ctx.stylize(`[External: ${address}]`, 'special'); + // } + return `${getCtxStyle(value, constructor, tag)}{}`; + } + braces[0] = `${getCtxStyle(value, constructor, tag)}{`; + } + } + + if (recurseTimes > ctx.depth && ctx.depth !== null) { + let constructorName = getCtxStyle(value, constructor, tag).slice(0, -1); + if (constructor !== null) { + constructorName = `[${constructorName}]`; + } + return ctx.stylize(constructorName, "special"); + } + recurseTimes += 1; + + ctx.seen.push(value); + ctx.currentDepth = recurseTimes; + let output; + const indentationLvl = ctx.indentationLvl; + try { + output = formatter(ctx, value, recurseTimes); + for (i = 0; i < keys.length; i++) { + output.push( + formatProperty(ctx, value, recurseTimes, keys[i], extrasType), + ); + } + if (protoProps !== undefined) { + output.push(...protoProps); + } + } catch (err) { + const constructorName = getCtxStyle(value, constructor, tag).slice(0, -1); + return handleMaxCallStackSize(ctx, err, constructorName, indentationLvl); + } + if (ctx.circular !== undefined) { + const index = ctx.circular.get(value); + if (index !== undefined) { + const reference = ctx.stylize(`<ref *${index}>`, "special"); + // Add reference always to the very beginning of the output. + if (ctx.compact !== true) { + base = base === "" ? reference : `${reference} ${base}`; + } else { + braces[0] = `${reference} ${braces[0]}`; + } + } + } + ctx.seen.pop(); + + if (ctx.sorted) { + const comparator = ctx.sorted === true ? undefined : ctx.sorted; + if (extrasType === kObjectType) { + output = output.sort(comparator); + } else if (keys.length > 1) { + const sorted = output.slice(output.length - keys.length).sort(comparator); + output.splice(output.length - keys.length, keys.length, ...sorted); + } + } + + const res = reduceToSingleString( + ctx, + output, + base, + braces, + extrasType, + recurseTimes, + value, + ); + const budget = ctx.budget[ctx.indentationLvl] || 0; + const newLength = budget + res.length; + ctx.budget[ctx.indentationLvl] = newLength; + // If any indentationLvl exceeds this limit, limit further inspecting to the + // minimum. Otherwise the recursive algorithm might continue inspecting the + // object even though the maximum string size (~2 ** 28 on 32 bit systems and + // ~2 ** 30 on 64 bit systems) exceeded. The actual output is not limited at + // exactly 2 ** 27 but a bit higher. This depends on the object shape. + // This limit also makes sure that huge objects don't block the event loop + // significantly. + if (newLength > 2 ** 27) { + ctx.depth = -1; + } + return res; +} + +const builtInObjects = new Set( + Object.getOwnPropertyNames(globalThis).filter((e) => + /^[A-Z][a-zA-Z0-9]+$/.test(e) + ), +); + +function addPrototypeProperties( + ctx, + main, + obj, + recurseTimes, + output, +) { + let depth = 0; + let keys; + let keySet; + do { + if (depth !== 0 || main === obj) { + obj = Object.getPrototypeOf(obj); + // Stop as soon as a null prototype is encountered. + if (obj === null) { + return; + } + // Stop as soon as a built-in object type is detected. + const descriptor = Object.getOwnPropertyDescriptor(obj, "constructor"); + if ( + descriptor !== undefined && + typeof descriptor.value === "function" && + builtInObjects.has(descriptor.value.name) + ) { + return; + } + } + + if (depth === 0) { + keySet = new Set(); + } else { + Array.prototype.forEach.call(keys, (key) => keySet.add(key)); + } + // Get all own property names and symbols. + keys = Reflect.ownKeys(obj); + Array.prototype.push.call(ctx.seen, main); + for (const key of keys) { + // Ignore the `constructor` property and keys that exist on layers above. + if ( + key === "constructor" || + // deno-lint-ignore no-prototype-builtins + main.hasOwnProperty(key) || + (depth !== 0 && keySet.has(key)) + ) { + continue; + } + const desc = Object.getOwnPropertyDescriptor(obj, key); + if (typeof desc.value === "function") { + continue; + } + const value = formatProperty( + ctx, + obj, + recurseTimes, + key, + kObjectType, + desc, + main, + ); + if (ctx.colors) { + // Faint! + Array.prototype.push.call(output, `\u001b[2m${value}\u001b[22m`); + } else { + Array.prototype.push.call(output, value); + } + } + Array.prototype.pop.call(ctx.seen); + // Limit the inspection to up to three prototype layers. Using `recurseTimes` + // is not a good choice here, because it's as if the properties are declared + // on the current object from the users perspective. + } while (++depth !== 3); +} + +function getConstructorName( + obj, + ctx, + recurseTimes, + protoProps, +) { + let firstProto; + const tmp = obj; + while (obj || isUndetectableObject(obj)) { + const descriptor = Object.getOwnPropertyDescriptor(obj, "constructor"); + if ( + descriptor !== undefined && + typeof descriptor.value === "function" && + descriptor.value.name !== "" && + isInstanceof(tmp, descriptor.value) + ) { + if ( + protoProps !== undefined && + (firstProto !== obj || + !builtInObjects.has(descriptor.value.name)) + ) { + addPrototypeProperties( + ctx, + tmp, + firstProto || tmp, + recurseTimes, + protoProps, + ); + } + return descriptor.value.name; + } + + obj = Object.getPrototypeOf(obj); + if (firstProto === undefined) { + firstProto = obj; + } + } + + if (firstProto === null) { + return null; + } + + // TODO(wafuwafu13): Implement + // const res = internalGetConstructorName(tmp); + const res = undefined; + + if (recurseTimes > ctx.depth && ctx.depth !== null) { + return `${res} <Complex prototype>`; + } + + const protoConstr = getConstructorName( + firstProto, + ctx, + recurseTimes + 1, + protoProps, + ); + + if (protoConstr === null) { + return `${res} <${ + inspect(firstProto, { + ...ctx, + customInspect: false, + depth: -1, + }) + }>`; + } + + return `${res} <${protoConstr}>`; +} + +function formatPrimitive(fn, value, ctx) { + if (typeof value === "string") { + let trailer = ""; + if (value.length > ctx.maxStringLength) { + const remaining = value.length - ctx.maxStringLength; + value = value.slice(0, ctx.maxStringLength); + trailer = `... ${remaining} more character${remaining > 1 ? "s" : ""}`; + } + if ( + ctx.compact !== true && + // TODO(BridgeAR): Add unicode support. Use the readline getStringWidth + // function. + value.length > kMinLineLength && + value.length > ctx.breakLength - ctx.indentationLvl - 4 + ) { + return value + .split(/(?<=\n)/) + .map((line) => fn(strEscape(line), "string")) + .join(` +\n${" ".repeat(ctx.indentationLvl + 2)}`) + trailer; + } + return fn(strEscape(value), "string") + trailer; + } + if (typeof value === "number") { + return formatNumber(fn, value); + } + if (typeof value === "bigint") { + return formatBigInt(fn, value); + } + if (typeof value === "boolean") { + return fn(`${value}`, "boolean"); + } + if (typeof value === "undefined") { + return fn("undefined", "undefined"); + } + // es6 symbol primitive + return fn(value.toString(), "symbol"); +} + +// Return a new empty array to push in the results of the default formatter. +function getEmptyFormatArray() { + return []; +} + +function isInstanceof(object, proto) { + try { + return object instanceof proto; + } catch { + return false; + } +} + +function getPrefix(constructor, tag, fallback, size = "") { + if (constructor === null) { + if (tag !== "" && fallback !== tag) { + return `[${fallback}${size}: null prototype] [${tag}] `; + } + return `[${fallback}${size}: null prototype] `; + } + + if (tag !== "" && constructor !== tag) { + return `${constructor}${size} [${tag}] `; + } + return `${constructor}${size} `; +} + +function formatArray(ctx, value, recurseTimes) { + const valLen = value.length; + const len = Math.min(Math.max(0, ctx.maxArrayLength), valLen); + + const remaining = valLen - len; + const output = []; + for (let i = 0; i < len; i++) { + // Special handle sparse arrays. + // deno-lint-ignore no-prototype-builtins + if (!value.hasOwnProperty(i)) { + return formatSpecialArray(ctx, value, recurseTimes, len, output, i); + } + output.push(formatProperty(ctx, value, recurseTimes, i, kArrayType)); + } + if (remaining > 0) { + output.push(`... ${remaining} more item${remaining > 1 ? "s" : ""}`); + } + return output; +} + +function getCtxStyle(_value, constructor, tag) { + let fallback = ""; + if (constructor === null) { + // TODO(wafuwafu13): Implement + // fallback = internalGetConstructorName(value); + if (fallback === tag) { + fallback = "Object"; + } + } + return getPrefix(constructor, tag, fallback); +} + +// Look up the keys of the object. +function getKeys(value, showHidden) { + let keys; + const symbols = Object.getOwnPropertySymbols(value); + if (showHidden) { + keys = Object.getOwnPropertyNames(value); + if (symbols.length !== 0) { + Array.prototype.push.apply(keys, symbols); + } + } else { + // This might throw if `value` is a Module Namespace Object from an + // unevaluated module, but we don't want to perform the actual type + // check because it's expensive. + // TODO(devsnek): track https://github.com/tc39/ecma262/issues/1209 + // and modify this logic as needed. + try { + keys = Object.keys(value); + } catch (_err) { + // TODO(wafuwafu13): Implement + // assert(isNativeError(err) && err.name === 'ReferenceError' && + // isModuleNamespaceObject(value)); + keys = Object.getOwnPropertyNames(value); + } + if (symbols.length !== 0) { + // TODO(wafuwafu13): Implement + // const filter = (key: any) => + // + // Object.prototype.propertyIsEnumerable(value, key); + // Array.prototype.push.apply( + // keys, + // symbols.filter(filter), + // ); + } + } + return keys; +} + +function formatSet(value, ctx, _ignored, recurseTimes) { + const output = []; + ctx.indentationLvl += 2; + for (const v of value) { + Array.prototype.push.call(output, formatValue(ctx, v, recurseTimes)); + } + ctx.indentationLvl -= 2; + return output; +} + +function formatMap(value, ctx, _gnored, recurseTimes) { + const output = []; + ctx.indentationLvl += 2; + for (const { 0: k, 1: v } of value) { + output.push( + `${formatValue(ctx, k, recurseTimes)} => ${ + formatValue(ctx, v, recurseTimes) + }`, + ); + } + ctx.indentationLvl -= 2; + return output; +} + +function formatTypedArray( + value, + length, + ctx, + _ignored, + recurseTimes, +) { + const maxLength = Math.min(Math.max(0, ctx.maxArrayLength), length); + const remaining = value.length - maxLength; + const output = new Array(maxLength); + const elementFormatter = value.length > 0 && typeof value[0] === "number" + ? formatNumber + : formatBigInt; + for (let i = 0; i < maxLength; ++i) { + output[i] = elementFormatter(ctx.stylize, value[i]); + } + if (remaining > 0) { + output[maxLength] = `... ${remaining} more item${remaining > 1 ? "s" : ""}`; + } + if (ctx.showHidden) { + // .buffer goes last, it's not a primitive like the others. + // All besides `BYTES_PER_ELEMENT` are actually getters. + ctx.indentationLvl += 2; + for ( + const key of [ + "BYTES_PER_ELEMENT", + "length", + "byteLength", + "byteOffset", + "buffer", + ] + ) { + const str = formatValue(ctx, value[key], recurseTimes, true); + Array.prototype.push.call(output, `[${key}]: ${str}`); + } + ctx.indentationLvl -= 2; + } + return output; +} + +function getIteratorBraces(type, tag) { + if (tag !== `${type} Iterator`) { + if (tag !== "") { + tag += "] ["; + } + tag += `${type} Iterator`; + } + return [`[${tag}] {`, "}"]; +} + +function formatIterator(braces, ctx, value, recurseTimes) { + // TODO(wafuwafu13): Implement + // const { 0: entries, 1: isKeyValue } = previewEntries(value, true); + const { 0: entries, 1: isKeyValue } = value; + if (isKeyValue) { + // Mark entry iterators as such. + braces[0] = braces[0].replace(/ Iterator] {$/, " Entries] {"); + return formatMapIterInner(ctx, recurseTimes, entries, kMapEntries); + } + + return formatSetIterInner(ctx, recurseTimes, entries, kIterator); +} + +function getFunctionBase(value, constructor, tag) { + const stringified = Function.prototype.toString.call(value); + if (stringified.slice(0, 5) === "class" && stringified.endsWith("}")) { + const slice = stringified.slice(5, -1); + const bracketIndex = slice.indexOf("{"); + if ( + bracketIndex !== -1 && + (!slice.slice(0, bracketIndex).includes("(") || + // Slow path to guarantee that it's indeed a class. + classRegExp.test(slice.replace(stripCommentsRegExp))) + ) { + return getClassBase(value, constructor, tag); + } + } + let type = "Function"; + if (types.isGeneratorFunction(value)) { + type = `Generator${type}`; + } + if (types.isAsyncFunction(value)) { + type = `Async${type}`; + } + let base = `[${type}`; + if (constructor === null) { + base += " (null prototype)"; + } + if (value.name === "") { + base += " (anonymous)"; + } else { + base += `: ${value.name}`; + } + base += "]"; + if (constructor !== type && constructor !== null) { + base += ` ${constructor}`; + } + if (tag !== "" && constructor !== tag) { + base += ` [${tag}]`; + } + return base; +} + +function formatError( + err, + constructor, + tag, + ctx, + keys, +) { + const name = err.name != null ? String(err.name) : "Error"; + let len = name.length; + let stack = err.stack ? String(err.stack) : err.toString(); + + // Do not "duplicate" error properties that are already included in the output + // otherwise. + if (!ctx.showHidden && keys.length !== 0) { + for (const name of ["name", "message", "stack"]) { + const index = keys.indexOf(name); + // Only hide the property in case it's part of the original stack + if (index !== -1 && stack.includes(err[name])) { + keys.splice(index, 1); + } + } + } + + // A stack trace may contain arbitrary data. Only manipulate the output + // for "regular errors" (errors that "look normal") for now. + if ( + constructor === null || + (name.endsWith("Error") && + stack.startsWith(name) && + (stack.length === len || stack[len] === ":" || stack[len] === "\n")) + ) { + let fallback = "Error"; + if (constructor === null) { + const start = stack.match(/^([A-Z][a-z_ A-Z0-9[\]()-]+)(?::|\n {4}at)/) || + stack.match(/^([a-z_A-Z0-9-]*Error)$/); + fallback = (start && start[1]) || ""; + len = fallback.length; + fallback = fallback || "Error"; + } + const prefix = getPrefix(constructor, tag, fallback).slice(0, -1); + if (name !== prefix) { + if (prefix.includes(name)) { + if (len === 0) { + stack = `${prefix}: ${stack}`; + } else { + stack = `${prefix}${stack.slice(len)}`; + } + } else { + stack = `${prefix} [${name}]${stack.slice(len)}`; + } + } + } + // Ignore the error message if it's contained in the stack. + let pos = (err.message && stack.indexOf(err.message)) || -1; + if (pos !== -1) { + pos += err.message.length; + } + // Wrap the error in brackets in case it has no stack trace. + const stackStart = stack.indexOf("\n at", pos); + if (stackStart === -1) { + stack = `[${stack}]`; + } else if (ctx.colors) { + // Highlight userland code and node modules. + let newStack = stack.slice(0, stackStart); + const lines = stack.slice(stackStart + 1).split("\n"); + for (const line of lines) { + // const core = line.match(coreModuleRegExp); + // TODO(wafuwafu13): Implement + // if (core !== null && NativeModule.exists(core[1])) { + // newStack += `\n${ctx.stylize(line, 'undefined')}`; + // } else { + // This adds underscores to all node_modules to quickly identify them. + let nodeModule; + newStack += "\n"; + let pos = 0; + // deno-lint-ignore no-cond-assign + while (nodeModule = nodeModulesRegExp.exec(line)) { + // '/node_modules/'.length === 14 + newStack += line.slice(pos, nodeModule.index + 14); + newStack += ctx.stylize(nodeModule[1], "module"); + pos = nodeModule.index + nodeModule[0].length; + } + newStack += pos === 0 ? line : line.slice(pos); + // } + } + stack = newStack; + } + // The message and the stack have to be indented as well! + if (ctx.indentationLvl !== 0) { + const indentation = " ".repeat(ctx.indentationLvl); + stack = stack.replace(/\n/g, `\n${indentation}`); + } + return stack; +} + +let hexSlice; + +function formatArrayBuffer(ctx, value) { + let buffer; + try { + buffer = new Uint8Array(value); + } catch { + return [ctx.stylize("(detached)", "special")]; + } + // TODO(wafuwafu13): Implement + // if (hexSlice === undefined) + // hexSlice = uncurryThis(require('buffer').Buffer.prototype.hexSlice); + let str = hexSlice(buffer, 0, Math.min(ctx.maxArrayLength, buffer.length)) + .replace(/(.{2})/g, "$1 ").trim(); + + const remaining = buffer.length - ctx.maxArrayLength; + if (remaining > 0) { + str += ` ... ${remaining} more byte${remaining > 1 ? "s" : ""}`; + } + return [`${ctx.stylize("[Uint8Contents]", "special")}: <${str}>`]; +} + +function formatNumber(fn, value) { + // Format -0 as '-0'. Checking `value === -0` won't distinguish 0 from -0. + return fn(Object.is(value, -0) ? "-0" : `${value}`, "number"); +} + +function formatPromise(ctx, value, recurseTimes) { + let output; + // TODO(wafuwafu13): Implement + // const { 0: state, 1: result } = getPromiseDetails(value); + const { 0: state, 1: result } = value; + if (state === kPending) { + output = [ctx.stylize("<pending>", "special")]; + } else { + ctx.indentationLvl += 2; + const str = formatValue(ctx, result, recurseTimes); + ctx.indentationLvl -= 2; + output = [ + state === kRejected + ? `${ctx.stylize("<rejected>", "special")} ${str}` + : str, + ]; + } + return output; +} + +function formatWeakCollection(ctx) { + return [ctx.stylize("<items unknown>", "special")]; +} + +function formatWeakSet(ctx, value, recurseTimes) { + // TODO(wafuwafu13): Implement + // const entries = previewEntries(value); + const entries = value; + return formatSetIterInner(ctx, recurseTimes, entries, kWeak); +} + +function formatWeakMap(ctx, value, recurseTimes) { + // TODO(wafuwafu13): Implement + // const entries = previewEntries(value); + const entries = value; + return formatMapIterInner(ctx, recurseTimes, entries, kWeak); +} + +function formatProperty( + ctx, + value, + recurseTimes, + key, + type, + desc, + original = value, +) { + let name, str; + let extra = " "; + desc = desc || Object.getOwnPropertyDescriptor(value, key) || + { value: value[key], enumerable: true }; + if (desc.value !== undefined) { + const diff = (ctx.compact !== true || type !== kObjectType) ? 2 : 3; + ctx.indentationLvl += diff; + str = formatValue(ctx, desc.value, recurseTimes); + if (diff === 3 && ctx.breakLength < getStringWidth(str, ctx.colors)) { + extra = `\n${" ".repeat(ctx.indentationLvl)}`; + } + ctx.indentationLvl -= diff; + } else if (desc.get !== undefined) { + const label = desc.set !== undefined ? "Getter/Setter" : "Getter"; + const s = ctx.stylize; + const sp = "special"; + if ( + ctx.getters && (ctx.getters === true || + (ctx.getters === "get" && desc.set === undefined) || + (ctx.getters === "set" && desc.set !== undefined)) + ) { + try { + const tmp = desc.get.call(original); + ctx.indentationLvl += 2; + if (tmp === null) { + str = `${s(`[${label}:`, sp)} ${s("null", "null")}${s("]", sp)}`; + } else if (typeof tmp === "object") { + str = `${s(`[${label}]`, sp)} ${formatValue(ctx, tmp, recurseTimes)}`; + } else { + const primitive = formatPrimitive(s, tmp, ctx); + str = `${s(`[${label}:`, sp)} ${primitive}${s("]", sp)}`; + } + ctx.indentationLvl -= 2; + } catch (err) { + const message = `<Inspection threw (${err.message})>`; + str = `${s(`[${label}:`, sp)} ${message}${s("]", sp)}`; + } + } else { + str = ctx.stylize(`[${label}]`, sp); + } + } else if (desc.set !== undefined) { + str = ctx.stylize("[Setter]", "special"); + } else { + str = ctx.stylize("undefined", "undefined"); + } + if (type === kArrayType) { + return str; + } + if (typeof key === "symbol") { + const tmp = key.toString().replace(strEscapeSequencesReplacer, escapeFn); + + name = `[${ctx.stylize(tmp, "symbol")}]`; + } else if (key === "__proto__") { + name = "['__proto__']"; + } else if (desc.enumerable === false) { + const tmp = key.replace(strEscapeSequencesReplacer, escapeFn); + + name = `[${tmp}]`; + } else if (keyStrRegExp.test(key)) { + name = ctx.stylize(key, "name"); + } else { + name = ctx.stylize(strEscape(key), "string"); + } + return `${name}:${extra}${str}`; +} + +function handleMaxCallStackSize( + _ctx, + _err, + _constructorName, + _indentationLvl, +) { + // TODO(wafuwafu13): Implement + // if (types.isStackOverflowError(err)) { + // ctx.seen.pop(); + // ctx.indentationLvl = indentationLvl; + // return ctx.stylize( + // `[${constructorName}: Inspection interrupted ` + + // 'prematurely. Maximum call stack size exceeded.]', + // 'special' + // ); + // } + // /* c8 ignore next */ + // assert.fail(err.stack); +} + +// deno-lint-ignore no-control-regex +const colorRegExp = /\u001b\[\d\d?m/g; +function removeColors(str) { + return str.replace(colorRegExp, ""); +} + +function isBelowBreakLength(ctx, output, start, base) { + // Each entry is separated by at least a comma. Thus, we start with a total + // length of at least `output.length`. In addition, some cases have a + // whitespace in-between each other that is added to the total as well. + // TODO(BridgeAR): Add unicode support. Use the readline getStringWidth + // function. Check the performance overhead and make it an opt-in in case it's + // significant. + let totalLength = output.length + start; + if (totalLength + output.length > ctx.breakLength) { + return false; + } + for (let i = 0; i < output.length; i++) { + if (ctx.colors) { + totalLength += removeColors(output[i]).length; + } else { + totalLength += output[i].length; + } + if (totalLength > ctx.breakLength) { + return false; + } + } + // Do not line up properties on the same line if `base` contains line breaks. + return base === "" || !base.includes("\n"); +} + +function formatBigInt(fn, value) { + return fn(`${value}n`, "bigint"); +} + +function formatNamespaceObject( + keys, + ctx, + value, + recurseTimes, +) { + const output = new Array(keys.length); + for (let i = 0; i < keys.length; i++) { + try { + output[i] = formatProperty( + ctx, + value, + recurseTimes, + keys[i], + kObjectType, + ); + } catch (_err) { + // TODO(wafuwfu13): Implement + // assert(isNativeError(err) && err.name === 'ReferenceError'); + // Use the existing functionality. This makes sure the indentation and + // line breaks are always correct. Otherwise it is very difficult to keep + // this aligned, even though this is a hacky way of dealing with this. + const tmp = { [keys[i]]: "" }; + output[i] = formatProperty(ctx, tmp, recurseTimes, keys[i], kObjectType); + const pos = output[i].lastIndexOf(" "); + // We have to find the last whitespace and have to replace that value as + // it will be visualized as a regular string. + output[i] = output[i].slice(0, pos + 1) + + ctx.stylize("<uninitialized>", "special"); + } + } + // Reset the keys to an empty array. This prevents duplicated inspection. + keys.length = 0; + return output; +} + +// The array is sparse and/or has extra keys +function formatSpecialArray( + ctx, + value, + recurseTimes, + maxLength, + output, + i, +) { + const keys = Object.keys(value); + let index = i; + for (; i < keys.length && output.length < maxLength; i++) { + const key = keys[i]; + const tmp = +key; + // Arrays can only have up to 2^32 - 1 entries + if (tmp > 2 ** 32 - 2) { + break; + } + if (`${index}` !== key) { + if (!numberRegExp.test(key)) { + break; + } + const emptyItems = tmp - index; + const ending = emptyItems > 1 ? "s" : ""; + const message = `<${emptyItems} empty item${ending}>`; + output.push(ctx.stylize(message, "undefined")); + index = tmp; + if (output.length === maxLength) { + break; + } + } + output.push(formatProperty(ctx, value, recurseTimes, key, kArrayType)); + index++; + } + const remaining = value.length - index; + if (output.length !== maxLength) { + if (remaining > 0) { + const ending = remaining > 1 ? "s" : ""; + const message = `<${remaining} empty item${ending}>`; + output.push(ctx.stylize(message, "undefined")); + } + } else if (remaining > 0) { + output.push(`... ${remaining} more item${remaining > 1 ? "s" : ""}`); + } + return output; +} + +function getBoxedBase( + value, + ctx, + keys, + constructor, + tag, +) { + let type; + if (types.isNumberObject(value)) { + type = "Number"; + } else if (types.isStringObject(value)) { + type = "String"; + // For boxed Strings, we have to remove the 0-n indexed entries, + // since they just noisy up the output and are redundant + // Make boxed primitive Strings look like such + keys.splice(0, value.length); + } else if (types.isBooleanObject(value)) { + type = "Boolean"; + } else if (types.isBigIntObject(value)) { + type = "BigInt"; + } else { + type = "Symbol"; + } + let base = `[${type}`; + if (type !== constructor) { + if (constructor === null) { + base += " (null prototype)"; + } else { + base += ` (${constructor})`; + } + } + + base += `: ${formatPrimitive(stylizeNoColor, value.valueOf(), ctx)}]`; + if (tag !== "" && tag !== constructor) { + base += ` [${tag}]`; + } + if (keys.length !== 0 || ctx.stylize === stylizeNoColor) { + return base; + } + return ctx.stylize(base, type.toLowerCase()); +} + +function getClassBase(value, constructor, tag) { + // deno-lint-ignore no-prototype-builtins + const hasName = value.hasOwnProperty("name"); + const name = (hasName && value.name) || "(anonymous)"; + let base = `class ${name}`; + if (constructor !== "Function" && constructor !== null) { + base += ` [${constructor}]`; + } + if (tag !== "" && constructor !== tag) { + base += ` [${tag}]`; + } + if (constructor !== null) { + const superName = Object.getPrototypeOf(value).name; + if (superName) { + base += ` extends ${superName}`; + } + } else { + base += " extends [null prototype]"; + } + return `[${base}]`; +} + +function reduceToSingleString( + ctx, + output, + base, + braces, + extrasType, + recurseTimes, + value, +) { + if (ctx.compact !== true) { + if (typeof ctx.compact === "number" && ctx.compact >= 1) { + // Memorize the original output length. In case the output is grouped, + // prevent lining up the entries on a single line. + const entries = output.length; + // Group array elements together if the array contains at least six + // separate entries. + if (extrasType === kArrayExtrasType && entries > 6) { + output = groupArrayElements(ctx, output, value); + } + // `ctx.currentDepth` is set to the most inner depth of the currently + // inspected object part while `recurseTimes` is the actual current depth + // that is inspected. + // + // Example: + // + // const a = { first: [ 1, 2, 3 ], second: { inner: [ 1, 2, 3 ] } } + // + // The deepest depth of `a` is 2 (a.second.inner) and `a.first` has a max + // depth of 1. + // + // Consolidate all entries of the local most inner depth up to + // `ctx.compact`, as long as the properties are smaller than + // `ctx.breakLength`. + if ( + ctx.currentDepth - recurseTimes < ctx.compact && + entries === output.length + ) { + // Line up all entries on a single line in case the entries do not + // exceed `breakLength`. Add 10 as constant to start next to all other + // factors that may reduce `breakLength`. + const start = output.length + ctx.indentationLvl + + braces[0].length + base.length + 10; + if (isBelowBreakLength(ctx, output, start, base)) { + return `${base ? `${base} ` : ""}${braces[0]} ${join(output, ", ")}` + + ` ${braces[1]}`; + } + } + } + // Line up each entry on an individual line. + const indentation = `\n${" ".repeat(ctx.indentationLvl)}`; + return `${base ? `${base} ` : ""}${braces[0]}${indentation} ` + + `${join(output, `,${indentation} `)}${indentation}${braces[1]}`; + } + // Line up all entries on a single line in case the entries do not exceed + // `breakLength`. + if (isBelowBreakLength(ctx, output, 0, base)) { + return `${braces[0]}${base ? ` ${base}` : ""} ${join(output, ", ")} ` + + braces[1]; + } + const indentation = " ".repeat(ctx.indentationLvl); + // If the opening "brace" is too large, like in the case of "Set {", + // we need to force the first item to be on the next line or the + // items will not line up correctly. + const ln = base === "" && braces[0].length === 1 + ? " " + : `${base ? ` ${base}` : ""}\n${indentation} `; + // Line up each entry on an individual line. + return `${braces[0]}${ln}${join(output, `,\n${indentation} `)} ${braces[1]}`; +} + +// The built-in Array#join is slower in v8 6.0 +function join(output, separator) { + let str = ""; + if (output.length !== 0) { + const lastIndex = output.length - 1; + for (let i = 0; i < lastIndex; i++) { + // It is faster not to use a template string here + str += output[i]; + str += separator; + } + str += output[lastIndex]; + } + return str; +} + +function groupArrayElements(ctx, output, value) { + let totalLength = 0; + let maxLength = 0; + let i = 0; + let outputLength = output.length; + if (ctx.maxArrayLength < output.length) { + // This makes sure the "... n more items" part is not taken into account. + outputLength--; + } + const separatorSpace = 2; // Add 1 for the space and 1 for the separator. + const dataLen = new Array(outputLength); + // Calculate the total length of all output entries and the individual max + // entries length of all output entries. We have to remove colors first, + // otherwise the length would not be calculated properly. + for (; i < outputLength; i++) { + const len = getStringWidth(output[i], ctx.colors); + dataLen[i] = len; + totalLength += len + separatorSpace; + if (maxLength < len) { + maxLength = len; + } + } + // Add two to `maxLength` as we add a single whitespace character plus a comma + // in-between two entries. + const actualMax = maxLength + separatorSpace; + // Check if at least three entries fit next to each other and prevent grouping + // of arrays that contains entries of very different length (i.e., if a single + // entry is longer than 1/5 of all other entries combined). Otherwise the + // space in-between small entries would be enormous. + if ( + actualMax * 3 + ctx.indentationLvl < ctx.breakLength && + (totalLength / actualMax > 5 || maxLength <= 6) + ) { + const approxCharHeights = 2.5; + const averageBias = Math.sqrt(actualMax - totalLength / output.length); + const biasedMax = Math.max(actualMax - 3 - averageBias, 1); + // Dynamically check how many columns seem possible. + const columns = Math.min( + // Ideally a square should be drawn. We expect a character to be about 2.5 + // times as high as wide. This is the area formula to calculate a square + // which contains n rectangles of size `actualMax * approxCharHeights`. + // Divide that by `actualMax` to receive the correct number of columns. + // The added bias increases the columns for short entries. + Math.round( + Math.sqrt( + approxCharHeights * biasedMax * outputLength, + ) / biasedMax, + ), + // Do not exceed the breakLength. + Math.floor((ctx.breakLength - ctx.indentationLvl) / actualMax), + // Limit array grouping for small `compact` modes as the user requested + // minimal grouping. + ctx.compact * 4, + // Limit the columns to a maximum of fifteen. + 15, + ); + // Return with the original output if no grouping should happen. + if (columns <= 1) { + return output; + } + const tmp = []; + const maxLineLength = []; + for (let i = 0; i < columns; i++) { + let lineMaxLength = 0; + for (let j = i; j < output.length; j += columns) { + if (dataLen[j] > lineMaxLength) { + lineMaxLength = dataLen[j]; + } + } + lineMaxLength += separatorSpace; + maxLineLength[i] = lineMaxLength; + } + let order = String.prototype.padStart; + if (value !== undefined) { + for (let i = 0; i < output.length; i++) { + if (typeof value[i] !== "number" && typeof value[i] !== "bigint") { + order = String.prototype.padEnd; + break; + } + } + } + // Each iteration creates a single line of grouped entries. + for (let i = 0; i < outputLength; i += columns) { + // The last lines may contain less entries than columns. + const max = Math.min(i + columns, outputLength); + let str = ""; + let j = i; + for (; j < max - 1; j++) { + // Calculate extra color padding in case it's active. This has to be + // done line by line as some lines might contain more colors than + // others. + const padding = maxLineLength[j - i] + output[j].length - dataLen[j]; + str += `${output[j]}, `.padStart(padding, " "); + } + if (order === String.prototype.padStart) { + const padding = maxLineLength[j - i] + + output[j].length - + dataLen[j] - + separatorSpace; + str += output[j].padStart(padding, " "); + } else { + str += output[j]; + } + Array.prototype.push.call(tmp, str); + } + if (ctx.maxArrayLength < output.length) { + Array.prototype.push.call(tmp, output[outputLength]); + } + output = tmp; + } + return output; +} + +function formatMapIterInner( + ctx, + recurseTimes, + entries, + state, +) { + const maxArrayLength = Math.max(ctx.maxArrayLength, 0); + // Entries exist as [key1, val1, key2, val2, ...] + const len = entries.length / 2; + const remaining = len - maxArrayLength; + const maxLength = Math.min(maxArrayLength, len); + let output = new Array(maxLength); + let i = 0; + ctx.indentationLvl += 2; + if (state === kWeak) { + for (; i < maxLength; i++) { + const pos = i * 2; + output[i] = `${formatValue(ctx, entries[pos], recurseTimes)} => ${ + formatValue(ctx, entries[pos + 1], recurseTimes) + }`; + } + // Sort all entries to have a halfway reliable output (if more entries than + // retrieved ones exist, we can not reliably return the same output) if the + // output is not sorted anyway. + if (!ctx.sorted) { + output = output.sort(); + } + } else { + for (; i < maxLength; i++) { + const pos = i * 2; + const res = [ + formatValue(ctx, entries[pos], recurseTimes), + formatValue(ctx, entries[pos + 1], recurseTimes), + ]; + output[i] = reduceToSingleString( + ctx, + res, + "", + ["[", "]"], + kArrayExtrasType, + recurseTimes, + ); + } + } + ctx.indentationLvl -= 2; + if (remaining > 0) { + output.push(`... ${remaining} more item${remaining > 1 ? "s" : ""}`); + } + return output; +} + +function formatSetIterInner( + ctx, + recurseTimes, + entries, + state, +) { + const maxArrayLength = Math.max(ctx.maxArrayLength, 0); + const maxLength = Math.min(maxArrayLength, entries.length); + const output = new Array(maxLength); + ctx.indentationLvl += 2; + for (let i = 0; i < maxLength; i++) { + output[i] = formatValue(ctx, entries[i], recurseTimes); + } + ctx.indentationLvl -= 2; + if (state === kWeak && !ctx.sorted) { + // Sort all entries to have a halfway reliable output (if more entries than + // retrieved ones exist, we can not reliably return the same output) if the + // output is not sorted anyway. + output.sort(); + } + const remaining = entries.length - maxLength; + if (remaining > 0) { + Array.prototype.push.call( + output, + `... ${remaining} more item${remaining > 1 ? "s" : ""}`, + ); + } + return output; +} + +// Regex used for ansi escape code splitting +// Adopted from https://github.com/chalk/ansi-regex/blob/HEAD/index.js +// License: MIT, authors: @sindresorhus, Qix-, arjunmehta and LitoMore +// Matches all ansi escape code sequences in a string +const ansiPattern = "[\\u001B\\u009B][[\\]()#;?]*" + + "(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*" + + "|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)" + + "|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"; +const ansi = new RegExp(ansiPattern, "g"); + +/** + * Returns the number of columns required to display the given string. + */ +export function getStringWidth(str, removeControlChars = true) { + let width = 0; + + if (removeControlChars) { + str = stripVTControlCharacters(str); + } + str = str.normalize("NFC"); + for (const char of str[Symbol.iterator]()) { + const code = char.codePointAt(0); + if (isFullWidthCodePoint(code)) { + width += 2; + } else if (!isZeroWidthCodePoint(code)) { + width++; + } + } + + return width; +} + +/** + * Returns true if the character represented by a given + * Unicode code point is full-width. Otherwise returns false. + */ +const isFullWidthCodePoint = (code) => { + // Code points are partially derived from: + // https://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt + return code >= 0x1100 && ( + code <= 0x115f || // Hangul Jamo + code === 0x2329 || // LEFT-POINTING ANGLE BRACKET + code === 0x232a || // RIGHT-POINTING ANGLE BRACKET + // CJK Radicals Supplement .. Enclosed CJK Letters and Months + (code >= 0x2e80 && code <= 0x3247 && code !== 0x303f) || + // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A + (code >= 0x3250 && code <= 0x4dbf) || + // CJK Unified Ideographs .. Yi Radicals + (code >= 0x4e00 && code <= 0xa4c6) || + // Hangul Jamo Extended-A + (code >= 0xa960 && code <= 0xa97c) || + // Hangul Syllables + (code >= 0xac00 && code <= 0xd7a3) || + // CJK Compatibility Ideographs + (code >= 0xf900 && code <= 0xfaff) || + // Vertical Forms + (code >= 0xfe10 && code <= 0xfe19) || + // CJK Compatibility Forms .. Small Form Variants + (code >= 0xfe30 && code <= 0xfe6b) || + // Halfwidth and Fullwidth Forms + (code >= 0xff01 && code <= 0xff60) || + (code >= 0xffe0 && code <= 0xffe6) || + // Kana Supplement + (code >= 0x1b000 && code <= 0x1b001) || + // Enclosed Ideographic Supplement + (code >= 0x1f200 && code <= 0x1f251) || + // Miscellaneous Symbols and Pictographs 0x1f300 - 0x1f5ff + // Emoticons 0x1f600 - 0x1f64f + (code >= 0x1f300 && code <= 0x1f64f) || + // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane + (code >= 0x20000 && code <= 0x3fffd) + ); +}; + +const isZeroWidthCodePoint = (code) => { + return code <= 0x1F || // C0 control codes + (code >= 0x7F && code <= 0x9F) || // C1 control codes + (code >= 0x300 && code <= 0x36F) || // Combining Diacritical Marks + (code >= 0x200B && code <= 0x200F) || // Modifying Invisible Characters + // Combining Diacritical Marks for Symbols + (code >= 0x20D0 && code <= 0x20FF) || + (code >= 0xFE00 && code <= 0xFE0F) || // Variation Selectors + (code >= 0xFE20 && code <= 0xFE2F) || // Combining Half Marks + (code >= 0xE0100 && code <= 0xE01EF); // Variation Selectors +}; + +function hasBuiltInToString(value) { + // TODO(wafuwafu13): Implement + // // Prevent triggering proxy traps. + // const getFullProxy = false; + // const proxyTarget = getProxyDetails(value, getFullProxy); + const proxyTarget = undefined; + if (proxyTarget !== undefined) { + value = proxyTarget; + } + + // Count objects that have no `toString` function as built-in. + if (typeof value.toString !== "function") { + return true; + } + + // The object has a own `toString` property. Thus it's not not a built-in one. + if (Object.prototype.hasOwnProperty.call(value, "toString")) { + return false; + } + + // Find the object that has the `toString` property as own property in the + // prototype chain. + let pointer = value; + do { + pointer = Object.getPrototypeOf(pointer); + } while (!Object.prototype.hasOwnProperty.call(pointer, "toString")); + + // Check closer if the object is a built-in. + const descriptor = Object.getOwnPropertyDescriptor(pointer, "constructor"); + return descriptor !== undefined && + typeof descriptor.value === "function" && + builtInObjects.has(descriptor.value.name); +} + +const firstErrorLine = (error) => error.message.split("\n", 1)[0]; +let CIRCULAR_ERROR_MESSAGE; +function tryStringify(arg) { + try { + return JSON.stringify(arg); + } catch (err) { + // Populate the circular error message lazily + if (!CIRCULAR_ERROR_MESSAGE) { + try { + const a = {}; + a.a = a; + JSON.stringify(a); + } catch (circularError) { + CIRCULAR_ERROR_MESSAGE = firstErrorLine(circularError); + } + } + if ( + err.name === "TypeError" && + firstErrorLine(err) === CIRCULAR_ERROR_MESSAGE + ) { + return "[Circular]"; + } + throw err; + } +} + +export function format(...args) { + return formatWithOptionsInternal(undefined, args); +} + +export function formatWithOptions(inspectOptions, ...args) { + if (typeof inspectOptions !== "object" || inspectOptions === null) { + throw new codes.ERR_INVALID_ARG_TYPE( + "inspectOptions", + "object", + inspectOptions, + ); + } + return formatWithOptionsInternal(inspectOptions, args); +} + +function formatNumberNoColor(number, options) { + return formatNumber( + stylizeNoColor, + number, + options?.numericSeparator ?? inspectDefaultOptions.numericSeparator, + ); +} + +function formatBigIntNoColor(bigint, options) { + return formatBigInt( + stylizeNoColor, + bigint, + options?.numericSeparator ?? inspectDefaultOptions.numericSeparator, + ); +} + +function formatWithOptionsInternal(inspectOptions, args) { + const first = args[0]; + let a = 0; + let str = ""; + let join = ""; + + if (typeof first === "string") { + if (args.length === 1) { + return first; + } + let tempStr; + let lastPos = 0; + + for (let i = 0; i < first.length - 1; i++) { + if (first.charCodeAt(i) === 37) { // '%' + const nextChar = first.charCodeAt(++i); + if (a + 1 !== args.length) { + switch (nextChar) { + // deno-lint-ignore no-case-declarations + case 115: // 's' + const tempArg = args[++a]; + if (typeof tempArg === "number") { + tempStr = formatNumberNoColor(tempArg, inspectOptions); + } else if (typeof tempArg === "bigint") { + tempStr = formatBigIntNoColor(tempArg, inspectOptions); + } else if ( + typeof tempArg !== "object" || + tempArg === null || + !hasBuiltInToString(tempArg) + ) { + tempStr = String(tempArg); + } else { + tempStr = inspect(tempArg, { + ...inspectOptions, + compact: 3, + colors: false, + depth: 0, + }); + } + break; + case 106: // 'j' + tempStr = tryStringify(args[++a]); + break; + // deno-lint-ignore no-case-declarations + case 100: // 'd' + const tempNum = args[++a]; + if (typeof tempNum === "bigint") { + tempStr = formatBigIntNoColor(tempNum, inspectOptions); + } else if (typeof tempNum === "symbol") { + tempStr = "NaN"; + } else { + tempStr = formatNumberNoColor(Number(tempNum), inspectOptions); + } + break; + case 79: // 'O' + tempStr = inspect(args[++a], inspectOptions); + break; + case 111: // 'o' + tempStr = inspect(args[++a], { + ...inspectOptions, + showHidden: true, + showProxy: true, + depth: 4, + }); + break; + // deno-lint-ignore no-case-declarations + case 105: // 'i' + const tempInteger = args[++a]; + if (typeof tempInteger === "bigint") { + tempStr = formatBigIntNoColor(tempInteger, inspectOptions); + } else if (typeof tempInteger === "symbol") { + tempStr = "NaN"; + } else { + tempStr = formatNumberNoColor( + Number.parseInt(tempInteger), + inspectOptions, + ); + } + break; + // deno-lint-ignore no-case-declarations + case 102: // 'f' + const tempFloat = args[++a]; + if (typeof tempFloat === "symbol") { + tempStr = "NaN"; + } else { + tempStr = formatNumberNoColor( + Number.parseFloat(tempFloat), + inspectOptions, + ); + } + break; + case 99: // 'c' + a += 1; + tempStr = ""; + break; + case 37: // '%' + str += first.slice(lastPos, i); + lastPos = i + 1; + continue; + default: // Any other character is not a correct placeholder + continue; + } + if (lastPos !== i - 1) { + str += first.slice(lastPos, i - 1); + } + str += tempStr; + lastPos = i + 1; + } else if (nextChar === 37) { + str += first.slice(lastPos, i); + lastPos = i + 1; + } + } + } + if (lastPos !== 0) { + a++; + join = " "; + if (lastPos < first.length) { + str += first.slice(lastPos); + } + } + } + + while (a < args.length) { + const value = args[a]; + str += join; + str += typeof value !== "string" ? inspect(value, inspectOptions) : value; + join = " "; + a++; + } + return str; +} + +/** + * Remove all VT control characters. Use to estimate displayed string width. + */ +export function stripVTControlCharacters(str) { + validateString(str, "str"); + + return str.replace(ansi, ""); +} + +export default { + format, + getStringWidth, + inspect, + stripVTControlCharacters, + formatWithOptions, +}; diff --git a/ext/node/polyfills/internal/util/types.ts b/ext/node/polyfills/internal/util/types.ts new file mode 100644 index 000000000..299493bc9 --- /dev/null +++ b/ext/node/polyfills/internal/util/types.ts @@ -0,0 +1,143 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// +// Adapted from Node.js. 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 * as bindingTypes from "internal:deno_node/polyfills/internal_binding/types.ts"; +export { + isCryptoKey, + isKeyObject, +} from "internal:deno_node/polyfills/internal/crypto/_keys.ts"; + +// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag +const _getTypedArrayToStringTag = Object.getOwnPropertyDescriptor( + Object.getPrototypeOf(Uint8Array).prototype, + Symbol.toStringTag, +)!.get!; + +export function isArrayBufferView( + value: unknown, +): value is + | DataView + | BigInt64Array + | BigUint64Array + | Float32Array + | Float64Array + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint8ClampedArray + | Uint16Array + | Uint32Array { + return ArrayBuffer.isView(value); +} + +export function isBigInt64Array(value: unknown): value is BigInt64Array { + return _getTypedArrayToStringTag.call(value) === "BigInt64Array"; +} + +export function isBigUint64Array(value: unknown): value is BigUint64Array { + return _getTypedArrayToStringTag.call(value) === "BigUint64Array"; +} + +export function isFloat32Array(value: unknown): value is Float32Array { + return _getTypedArrayToStringTag.call(value) === "Float32Array"; +} + +export function isFloat64Array(value: unknown): value is Float64Array { + return _getTypedArrayToStringTag.call(value) === "Float64Array"; +} + +export function isInt8Array(value: unknown): value is Int8Array { + return _getTypedArrayToStringTag.call(value) === "Int8Array"; +} + +export function isInt16Array(value: unknown): value is Int16Array { + return _getTypedArrayToStringTag.call(value) === "Int16Array"; +} + +export function isInt32Array(value: unknown): value is Int32Array { + return _getTypedArrayToStringTag.call(value) === "Int32Array"; +} + +export function isTypedArray(value: unknown): value is + | BigInt64Array + | BigUint64Array + | Float32Array + | Float64Array + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint8ClampedArray + | Uint16Array + | Uint32Array { + return _getTypedArrayToStringTag.call(value) !== undefined; +} + +export function isUint8Array(value: unknown): value is Uint8Array { + return _getTypedArrayToStringTag.call(value) === "Uint8Array"; +} + +export function isUint8ClampedArray( + value: unknown, +): value is Uint8ClampedArray { + return _getTypedArrayToStringTag.call(value) === "Uint8ClampedArray"; +} + +export function isUint16Array(value: unknown): value is Uint16Array { + return _getTypedArrayToStringTag.call(value) === "Uint16Array"; +} + +export function isUint32Array(value: unknown): value is Uint32Array { + return _getTypedArrayToStringTag.call(value) === "Uint32Array"; +} + +export const { + // isExternal, + isDate, + isArgumentsObject, + isBigIntObject, + isBooleanObject, + isNumberObject, + isStringObject, + isSymbolObject, + isNativeError, + isRegExp, + isAsyncFunction, + isGeneratorFunction, + isGeneratorObject, + isPromise, + isMap, + isSet, + isMapIterator, + isSetIterator, + isWeakMap, + isWeakSet, + isArrayBuffer, + isDataView, + isSharedArrayBuffer, + isProxy, + isModuleNamespaceObject, + isAnyArrayBuffer, + isBoxedPrimitive, +} = bindingTypes; diff --git a/ext/node/polyfills/internal/validators.mjs b/ext/node/polyfills/internal/validators.mjs new file mode 100644 index 000000000..bea9e881a --- /dev/null +++ b/ext/node/polyfills/internal/validators.mjs @@ -0,0 +1,317 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { codes } from "internal:deno_node/polyfills/internal/error_codes.ts"; +import { hideStackFrames } from "internal:deno_node/polyfills/internal/hide_stack_frames.ts"; +import { isArrayBufferView } from "internal:deno_node/polyfills/internal/util/types.ts"; +import { normalizeEncoding } from "internal:deno_node/polyfills/internal/normalize_encoding.mjs"; + +/** + * @param {number} value + * @returns {boolean} + */ +function isInt32(value) { + return value === (value | 0); +} + +/** + * @param {unknown} value + * @returns {boolean} + */ +function isUint32(value) { + return value === (value >>> 0); +} + +const octalReg = /^[0-7]+$/; +const modeDesc = "must be a 32-bit unsigned integer or an octal string"; + +/** + * Parse and validate values that will be converted into mode_t (the S_* + * constants). Only valid numbers and octal strings are allowed. They could be + * converted to 32-bit unsigned integers or non-negative signed integers in the + * C++ land, but any value higher than 0o777 will result in platform-specific + * behaviors. + * + * @param {*} value Values to be validated + * @param {string} name Name of the argument + * @param {number} [def] If specified, will be returned for invalid values + * @returns {number} + */ +function parseFileMode(value, name, def) { + value ??= def; + if (typeof value === "string") { + if (!octalReg.test(value)) { + throw new codes.ERR_INVALID_ARG_VALUE(name, value, modeDesc); + } + value = Number.parseInt(value, 8); + } + + validateInt32(value, name, 0, 2 ** 32 - 1); + return value; +} + +const validateBuffer = hideStackFrames((buffer, name = "buffer") => { + if (!isArrayBufferView(buffer)) { + throw new codes.ERR_INVALID_ARG_TYPE( + name, + ["Buffer", "TypedArray", "DataView"], + buffer, + ); + } +}); + +const validateInteger = hideStackFrames( + ( + value, + name, + min = Number.MIN_SAFE_INTEGER, + max = Number.MAX_SAFE_INTEGER, + ) => { + if (typeof value !== "number") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value); + } + if (!Number.isInteger(value)) { + throw new codes.ERR_OUT_OF_RANGE(name, "an integer", value); + } + if (value < min || value > max) { + throw new codes.ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); + } + }, +); + +/** + * @param {unknown} value + * @param {string} name + * @param {{ + * allowArray?: boolean, + * allowFunction?: boolean, + * nullable?: boolean + * }} [options] + */ +const validateObject = hideStackFrames((value, name, options) => { + const useDefaultOptions = options == null; + const allowArray = useDefaultOptions ? false : options.allowArray; + const allowFunction = useDefaultOptions ? false : options.allowFunction; + const nullable = useDefaultOptions ? false : options.nullable; + if ( + (!nullable && value === null) || + (!allowArray && Array.isArray(value)) || + (typeof value !== "object" && ( + !allowFunction || typeof value !== "function" + )) + ) { + throw new codes.ERR_INVALID_ARG_TYPE(name, "Object", value); + } +}); + +const validateInt32 = hideStackFrames( + (value, name, min = -2147483648, max = 2147483647) => { + // The defaults for min and max correspond to the limits of 32-bit integers. + if (!isInt32(value)) { + if (typeof value !== "number") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value); + } + + if (!Number.isInteger(value)) { + throw new codes.ERR_OUT_OF_RANGE(name, "an integer", value); + } + + throw new codes.ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); + } + + if (value < min || value > max) { + throw new codes.ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); + } + }, +); + +const validateUint32 = hideStackFrames( + (value, name, positive) => { + if (!isUint32(value)) { + if (typeof value !== "number") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value); + } + if (!Number.isInteger(value)) { + throw new codes.ERR_OUT_OF_RANGE(name, "an integer", value); + } + const min = positive ? 1 : 0; + // 2 ** 32 === 4294967296 + throw new codes.ERR_OUT_OF_RANGE( + name, + `>= ${min} && < 4294967296`, + value, + ); + } + if (positive && value === 0) { + throw new codes.ERR_OUT_OF_RANGE(name, ">= 1 && < 4294967296", value); + } + }, +); + +/** + * @param {unknown} value + * @param {string} name + */ +function validateString(value, name) { + if (typeof value !== "string") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "string", value); + } +} + +/** + * @param {unknown} value + * @param {string} name + */ +function validateNumber(value, name) { + if (typeof value !== "number") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value); + } +} + +/** + * @param {unknown} value + * @param {string} name + */ +function validateBoolean(value, name) { + if (typeof value !== "boolean") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "boolean", value); + } +} + +/** + * @param {unknown} value + * @param {string} name + * @param {unknown[]} oneOf + */ +const validateOneOf = hideStackFrames( + (value, name, oneOf) => { + if (!Array.prototype.includes.call(oneOf, value)) { + const allowed = Array.prototype.join.call( + Array.prototype.map.call( + oneOf, + (v) => (typeof v === "string" ? `'${v}'` : String(v)), + ), + ", ", + ); + const reason = "must be one of: " + allowed; + + throw new codes.ERR_INVALID_ARG_VALUE(name, value, reason); + } + }, +); + +export function validateEncoding(data, encoding) { + const normalizedEncoding = normalizeEncoding(encoding); + const length = data.length; + + if (normalizedEncoding === "hex" && length % 2 !== 0) { + throw new codes.ERR_INVALID_ARG_VALUE( + "encoding", + encoding, + `is invalid for data of length ${length}`, + ); + } +} + +// Check that the port number is not NaN when coerced to a number, +// is an integer and that it falls within the legal range of port numbers. +/** + * @param {string} name + * @returns {number} + */ +function validatePort(port, name = "Port", allowZero = true) { + if ( + (typeof port !== "number" && typeof port !== "string") || + (typeof port === "string" && + String.prototype.trim.call(port).length === 0) || + +port !== (+port >>> 0) || + port > 0xFFFF || + (port === 0 && !allowZero) + ) { + throw new codes.ERR_SOCKET_BAD_PORT(name, port, allowZero); + } + + return port; +} + +/** + * @param {unknown} signal + * @param {string} name + */ +const validateAbortSignal = hideStackFrames( + (signal, name) => { + if ( + signal !== undefined && + (signal === null || + typeof signal !== "object" || + !("aborted" in signal)) + ) { + throw new codes.ERR_INVALID_ARG_TYPE(name, "AbortSignal", signal); + } + }, +); + +/** + * @param {unknown} value + * @param {string} name + */ +const validateFunction = hideStackFrames( + (value, name) => { + if (typeof value !== "function") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "Function", value); + } + }, +); + +/** + * @param {unknown} value + * @param {string} name + */ +const validateArray = hideStackFrames( + (value, name, minLength = 0) => { + if (!Array.isArray(value)) { + throw new codes.ERR_INVALID_ARG_TYPE(name, "Array", value); + } + if (value.length < minLength) { + const reason = `must be longer than ${minLength}`; + throw new codes.ERR_INVALID_ARG_VALUE(name, value, reason); + } + }, +); + +export default { + isInt32, + isUint32, + parseFileMode, + validateAbortSignal, + validateArray, + validateBoolean, + validateBuffer, + validateFunction, + validateInt32, + validateInteger, + validateNumber, + validateObject, + validateOneOf, + validatePort, + validateString, + validateUint32, +}; +export { + isInt32, + isUint32, + parseFileMode, + validateAbortSignal, + validateArray, + validateBoolean, + validateBuffer, + validateFunction, + validateInt32, + validateInteger, + validateNumber, + validateObject, + validateOneOf, + validatePort, + validateString, + validateUint32, +}; |