From 68b388a93a3efe443fc5e306e883847bfb8551db Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Mon, 14 Oct 2024 14:00:02 +0530 Subject: fix(ext/node): allow writing to tty columns (#26201) Behave similar to Node.js where modifying `stdout.columns` doesn't really resize the terminal. Ref https://github.com/nodejs/node/issues/17529 Fixes https://github.com/denoland/deno/issues/26196 --- ext/node/polyfills/_process/streams.mjs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/_process/streams.mjs b/ext/node/polyfills/_process/streams.mjs index 7936e82aa..3573956c9 100644 --- a/ext/node/polyfills/_process/streams.mjs +++ b/ext/node/polyfills/_process/streams.mjs @@ -66,14 +66,19 @@ export function createWritableStdioStream(writer, name, warmup = false) { // We cannot call `writer?.isTerminal()` eagerly here let getIsTTY = () => writer?.isTerminal(); + const getColumns = () => + stream._columns || + (writer?.isTerminal() ? Deno.consoleSize?.().columns : undefined); ObjectDefineProperties(stream, { columns: { __proto__: null, enumerable: true, configurable: true, - get: () => - writer?.isTerminal() ? Deno.consoleSize?.().columns : undefined, + get: () => getColumns(), + set: (value) => { + stream._columns = value; + }, }, rows: { __proto__: null, -- cgit v1.2.3 From c7153838ec332d474c32b464b94f99382028bbbc Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Tue, 15 Oct 2024 14:47:12 +0530 Subject: fix(ext/node): implement TCP.setNoDelay (#26263) Fixes https://github.com/denoland/deno/issues/26177 The significant delay was caused by Nagel's algorithm + delayed ACKs in Linux kernels. Here's the [kernel patch](https://lwn.net/Articles/502585/) which added 40ms `tcp_default_delack_min` ``` $ deno run -A pg-bench.mjs # main Tue Oct 15 2024 12:27:22 GMT+0530 (India Standard Time): 42ms $ target/release/deno run -A pg-bench.mjs # this patch Tue Oct 15 2024 12:28:02 GMT+0530 (India Standard Time): 1ms ``` ```js import { Buffer } from "node:buffer"; import pg from 'pg' const { Client } = pg const client = new Client({ connectionString: 'postgresql://postgres:postgres@127.0.0.1:5432/postgres' }) await client.connect() async function fetch() { const startPerf = performance.now(); const res = await client.query(`select $1::int as int, $2 as string, $3::timestamp with time zone as timestamp, $4 as null, $5::bool as boolean, $6::bytea as bytea, $7::jsonb as json `, [ 1337, 'wat', new Date().toISOString(), null, false, Buffer.from('awesome'), JSON.stringify([{ some: 'json' }, { array: 'object' }]) ]) console.log(`${new Date()}: ${Math.round(performance.now() - startPerf)}ms`) } for(;;) await fetch(); ``` --- ext/node/polyfills/internal_binding/tcp_wrap.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/internal_binding/tcp_wrap.ts b/ext/node/polyfills/internal_binding/tcp_wrap.ts index 973a1d1c0..1321cc627 100644 --- a/ext/node/polyfills/internal_binding/tcp_wrap.ts +++ b/ext/node/polyfills/internal_binding/tcp_wrap.ts @@ -299,8 +299,8 @@ export class TCP extends ConnectionWrap { * @param noDelay * @return An error status code. */ - setNoDelay(_noDelay: boolean): number { - // TODO(bnoordhuis) https://github.com/denoland/deno/pull/13103 + setNoDelay(noDelay: boolean): number { + this[kStreamBaseField].setNoDelay(noDelay); return 0; } -- cgit v1.2.3 From c5a7f98d82b64408764dfd269a17b5ddb6974737 Mon Sep 17 00:00:00 2001 From: Toby Ealden Date: Tue, 15 Oct 2024 19:05:10 +0100 Subject: fix(ext/node): handle http2 server ending stream (#26235) Closes #24845 --- ext/node/polyfills/http2.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts index a9ced2bd9..dc2379aeb 100644 --- a/ext/node/polyfills/http2.ts +++ b/ext/node/polyfills/http2.ts @@ -882,6 +882,7 @@ export class ClientHttp2Stream extends Duplex { trailersReady: false, endAfterHeaders: false, shutdownWritableCalled: false, + serverEndedCall: false, }; this[kDenoResponse] = undefined; this[kDenoRid] = undefined; @@ -1109,7 +1110,9 @@ export class ClientHttp2Stream extends Duplex { } debugHttp2(">>> chunk", chunk, finished, this[kDenoResponse].bodyRid); - if (chunk === null) { + if (finished || chunk === null) { + this[kState].serverEndedCall = true; + const trailerList = await op_http2_client_get_response_trailers( this[kDenoResponse].bodyRid, ); @@ -1237,7 +1240,9 @@ export class ClientHttp2Stream extends Duplex { this[kSession] = undefined; session[kMaybeDestroy](); - callback(err); + if (callback) { + callback(err); + } } [kMaybeDestroy](code = constants.NGHTTP2_NO_ERROR) { @@ -1280,6 +1285,9 @@ function shutdownWritable(stream, callback, streamRid) { if (state.flags & STREAM_FLAGS_HAS_TRAILERS) { onStreamTrailers(stream); callback(); + } else if (state.serverEndedCall) { + debugHttp2(">>> stream finished"); + callback(); } else { op_http2_client_send_data(streamRid, new Uint8Array(), true) .then(() => { -- cgit v1.2.3 From 7211028f1e3b0ef2eaa4053d66b8bd9972731131 Mon Sep 17 00:00:00 2001 From: Kenta Moriuchi Date: Wed, 16 Oct 2024 03:32:27 +0900 Subject: fix(ext/node): use primordials in `ext/node/polyfills/internal/buffer.mjs` (#24993) Towards #24236 --- ext/node/polyfills/internal/buffer.mjs | 575 +++++++++++++++++++-------------- 1 file changed, 331 insertions(+), 244 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/internal/buffer.mjs b/ext/node/polyfills/internal/buffer.mjs index 6687f7394..dd549221f 100644 --- a/ext/node/polyfills/internal/buffer.mjs +++ b/ext/node/polyfills/internal/buffer.mjs @@ -2,10 +2,59 @@ // Copyright Joyent and Node contributors. All rights reserved. MIT license. // Copyright Feross Aboukhadijeh, and other contributors. All rights reserved. MIT license. -// TODO(petamoriken): enable prefer-primordials for node polyfills -// deno-lint-ignore-file prefer-primordials - -import { core } from "ext:core/mod.js"; +import { core, primordials } from "ext:core/mod.js"; +const { + isAnyArrayBuffer, + isArrayBuffer, + isDataView, + isSharedArrayBuffer, + isTypedArray, +} = core; +const { + ArrayBufferPrototypeGetByteLength, + ArrayBufferPrototypeGetDetached, + ArrayIsArray, + ArrayPrototypeSlice, + BigInt, + DataViewPrototypeGetByteLength, + Float32Array, + Float64Array, + MathFloor, + MathMin, + Number, + NumberIsInteger, + NumberIsNaN, + NumberMAX_SAFE_INTEGER, + NumberMIN_SAFE_INTEGER, + NumberPrototypeToString, + ObjectCreate, + ObjectDefineProperty, + ObjectPrototypeIsPrototypeOf, + ObjectSetPrototypeOf, + RangeError, + SafeRegExp, + String, + StringFromCharCode, + StringPrototypeCharCodeAt, + StringPrototypeIncludes, + StringPrototypeReplace, + StringPrototypeToLowerCase, + StringPrototypeTrim, + SymbolFor, + SymbolToPrimitive, + TypeError, + TypeErrorPrototype, + TypedArrayPrototypeCopyWithin, + TypedArrayPrototypeFill, + TypedArrayPrototypeGetBuffer, + TypedArrayPrototypeGetByteLength, + TypedArrayPrototypeGetByteOffset, + TypedArrayPrototypeSet, + TypedArrayPrototypeSlice, + TypedArrayPrototypeSubarray, + Uint8Array, + Uint8ArrayPrototype, +} = primordials; import { op_is_ascii, op_is_utf8, op_transcode } from "ext:core/ops"; import { TextDecoder, TextEncoder } from "ext:deno_web/08_text_encoding.js"; @@ -24,11 +73,6 @@ import { hexToBytes, utf16leToBytes, } from "ext:deno_node/internal_binding/_utils.ts"; -import { - isAnyArrayBuffer, - isArrayBufferView, - isTypedArray, -} from "ext:deno_node/internal/util/types.ts"; import { normalizeEncoding } from "ext:deno_node/internal/util.mjs"; import { validateBuffer } from "ext:deno_node/internal/validators.mjs"; import { isUint8Array } from "ext:deno_node/internal/util/types.ts"; @@ -50,9 +94,13 @@ const utf8Encoder = new TextEncoder(); // Temporary buffers to convert numbers. const float32Array = new Float32Array(1); -const uInt8Float32Array = new Uint8Array(float32Array.buffer); +const uInt8Float32Array = new Uint8Array( + TypedArrayPrototypeGetBuffer(float32Array), +); const float64Array = new Float64Array(1); -const uInt8Float64Array = new Uint8Array(float64Array.buffer); +const uInt8Float64Array = new Uint8Array( + TypedArrayPrototypeGetBuffer(float64Array), +); // Check endianness. float32Array[0] = -1; // 0xBF800000 @@ -64,10 +112,7 @@ export const kMaxLength = 2147483647; export const kStringMaxLength = 536870888; const MAX_UINT32 = 2 ** 32; -const customInspectSymbol = - typeof Symbol === "function" && typeof Symbol["for"] === "function" - ? Symbol["for"]("nodejs.util.inspect.custom") - : null; +const customInspectSymbol = SymbolFor("nodejs.util.inspect.custom"); export const INSPECT_MAX_BYTES = 50; @@ -76,23 +121,25 @@ export const constants = { MAX_STRING_LENGTH: kStringMaxLength, }; -Object.defineProperty(Buffer.prototype, "parent", { +ObjectDefineProperty(Buffer.prototype, "parent", { + __proto__: null, enumerable: true, get: function () { - if (!Buffer.isBuffer(this)) { + if (!BufferIsBuffer(this)) { return void 0; } - return this.buffer; + return TypedArrayPrototypeGetBuffer(this); }, }); -Object.defineProperty(Buffer.prototype, "offset", { +ObjectDefineProperty(Buffer.prototype, "offset", { + __proto__: null, enumerable: true, get: function () { - if (!Buffer.isBuffer(this)) { + if (!BufferIsBuffer(this)) { return void 0; } - return this.byteOffset; + return TypedArrayPrototypeGetByteOffset(this); }, }); @@ -103,10 +150,21 @@ function createBuffer(length) { ); } const buf = new Uint8Array(length); - Object.setPrototypeOf(buf, Buffer.prototype); + ObjectSetPrototypeOf(buf, BufferPrototype); return buf; } +/** + * @param {ArrayBufferLike} O + * @returns {boolean} + */ +function isDetachedBuffer(O) { + if (isSharedArrayBuffer(O)) { + return false; + } + return ArrayBufferPrototypeGetDetached(O); +} + export function Buffer(arg, encodingOrOffset, length) { if (typeof arg === "number") { if (typeof encodingOrOffset === "string") { @@ -133,6 +191,7 @@ function _from(value, encodingOrOffset, length) { return fromArrayBuffer(value, encodingOrOffset, length); } + // deno-lint-ignore prefer-primordials const valueOf = value.valueOf && value.valueOf(); if ( valueOf != null && @@ -147,8 +206,8 @@ function _from(value, encodingOrOffset, length) { return b; } - if (typeof value[Symbol.toPrimitive] === "function") { - const primitive = value[Symbol.toPrimitive]("string"); + if (typeof value[SymbolToPrimitive] === "function") { + const primitive = value[SymbolToPrimitive]("string"); if (typeof primitive === "string") { return fromString(primitive, encodingOrOffset); } @@ -162,13 +221,19 @@ function _from(value, encodingOrOffset, length) { ); } -Buffer.from = function from(value, encodingOrOffset, length) { +const BufferFrom = Buffer.from = function from( + value, + encodingOrOffset, + length, +) { return _from(value, encodingOrOffset, length); }; -Object.setPrototypeOf(Buffer.prototype, Uint8Array.prototype); +const BufferPrototype = Buffer.prototype; + +ObjectSetPrototypeOf(Buffer.prototype, Uint8ArrayPrototype); -Object.setPrototypeOf(Buffer, Uint8Array); +ObjectSetPrototypeOf(Buffer, Uint8Array); function assertSize(size) { validateNumber(size, "size", 0, kMaxLength); @@ -186,6 +251,7 @@ function _alloc(size, fill, encoding) { encoding, ); } + // deno-lint-ignore prefer-primordials return buffer.fill(fill, encoding); } return buffer; @@ -212,13 +278,14 @@ function fromString(string, encoding) { if (typeof encoding !== "string" || encoding === "") { encoding = "utf8"; } - if (!Buffer.isEncoding(encoding)) { + if (!BufferIsEncoding(encoding)) { throw new codes.ERR_UNKNOWN_ENCODING(encoding); } const length = byteLength(string, encoding) | 0; let buf = createBuffer(length); const actual = buf.write(string, encoding); if (actual !== length) { + // deno-lint-ignore prefer-primordials buf = buf.slice(0, actual); } return buf; @@ -226,11 +293,12 @@ function fromString(string, encoding) { function fromArrayLike(obj) { const buf = new Uint8Array(obj); - Object.setPrototypeOf(buf, Buffer.prototype); + ObjectSetPrototypeOf(buf, BufferPrototype); return buf; } function fromObject(obj) { + // deno-lint-ignore prefer-primordials if (obj.length !== undefined || isAnyArrayBuffer(obj.buffer)) { if (typeof obj.length !== "number") { return createBuffer(0); @@ -239,7 +307,7 @@ function fromObject(obj) { return fromArrayLike(obj); } - if (obj.type === "Buffer" && Array.isArray(obj.data)) { + if (obj.type === "Buffer" && ArrayIsArray(obj.data)) { return fromArrayLike(obj.data); } } @@ -248,7 +316,7 @@ function checked(length) { if (length >= kMaxLength) { throw new RangeError( "Attempt to allocate Buffer larger than maximum size: 0x" + - kMaxLength.toString(16) + " bytes", + NumberPrototypeToString(kMaxLength, 16) + " bytes", ); } return length | 0; @@ -256,25 +324,33 @@ function checked(length) { export function SlowBuffer(length) { assertSize(length); - return Buffer.alloc(+length); + return _alloc(+length); } -Object.setPrototypeOf(SlowBuffer.prototype, Uint8Array.prototype); +ObjectSetPrototypeOf(SlowBuffer.prototype, Uint8ArrayPrototype); -Object.setPrototypeOf(SlowBuffer, Uint8Array); +ObjectSetPrototypeOf(SlowBuffer, Uint8Array); -Buffer.isBuffer = function isBuffer(b) { - return b != null && b._isBuffer === true && b !== Buffer.prototype; +const BufferIsBuffer = Buffer.isBuffer = function isBuffer(b) { + return b != null && b._isBuffer === true && b !== BufferPrototype; }; -Buffer.compare = function compare(a, b) { - if (isInstance(a, Uint8Array)) { - a = Buffer.from(a, a.offset, a.byteLength); +const BufferCompare = Buffer.compare = function compare(a, b) { + if (isUint8Array(a)) { + a = BufferFrom( + a, + TypedArrayPrototypeGetByteOffset(a), + TypedArrayPrototypeGetByteLength(a), + ); } - if (isInstance(b, Uint8Array)) { - b = Buffer.from(b, b.offset, b.byteLength); + if (isUint8Array(b)) { + b = BufferFrom( + b, + TypedArrayPrototypeGetByteOffset(b), + TypedArrayPrototypeGetByteLength(b), + ); } - if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + if (!BufferIsBuffer(a) || !BufferIsBuffer(b)) { throw new TypeError( 'The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array', ); @@ -284,7 +360,7 @@ Buffer.compare = function compare(a, b) { } let x = a.length; let y = b.length; - for (let i = 0, len = Math.min(x, y); i < len; ++i) { + for (let i = 0, len = MathMin(x, y); i < len; ++i) { if (a[i] !== b[i]) { x = a[i]; y = b[i]; @@ -300,18 +376,18 @@ Buffer.compare = function compare(a, b) { return 0; }; -Buffer.isEncoding = function isEncoding(encoding) { +const BufferIsEncoding = Buffer.isEncoding = function isEncoding(encoding) { return typeof encoding === "string" && encoding.length !== 0 && normalizeEncoding(encoding) !== undefined; }; Buffer.concat = function concat(list, length) { - if (!Array.isArray(list)) { + if (!ArrayIsArray(list)) { throw new codes.ERR_INVALID_ARG_TYPE("list", "Array", list); } if (list.length === 0) { - return Buffer.alloc(0); + return _alloc(0); } if (length === undefined) { @@ -325,7 +401,7 @@ Buffer.concat = function concat(list, length) { validateOffset(length, "length"); } - const buffer = Buffer.allocUnsafe(length); + const buffer = _allocUnsafe(length); let pos = 0; for (let i = 0; i < list.length; i++) { const buf = list[i]; @@ -346,7 +422,7 @@ Buffer.concat = function concat(list, length) { // Zero-fill the remaining bytes if the specified `length` was more than // the actual total length, i.e. if we have some remaining allocated bytes // there were not initialized. - buffer.fill(0, pos, length); + TypedArrayPrototypeFill(buffer, 0, pos, length); } return buffer; @@ -354,7 +430,18 @@ Buffer.concat = function concat(list, length) { function byteLength(string, encoding) { if (typeof string !== "string") { - if (isArrayBufferView(string) || isAnyArrayBuffer(string)) { + if (isTypedArray(string)) { + return TypedArrayPrototypeGetByteLength(string); + } + if (isDataView(string)) { + return DataViewPrototypeGetByteLength(string); + } + if (isArrayBuffer(string)) { + return ArrayBufferPrototypeGetByteLength(string); + } + if (isSharedArrayBuffer(string)) { + // TODO(petamoriken): add SharedArayBuffer to primordials + // deno-lint-ignore prefer-primordials return string.byteLength; } @@ -463,6 +550,7 @@ Buffer.prototype.toString = function toString(encoding, start, end) { throw new codes.ERR_UNKNOWN_ENCODING(encoding); } + // deno-lint-ignore prefer-primordials return ops.slice(this, start, end); }; @@ -479,22 +567,29 @@ Buffer.prototype.equals = function equals(b) { if (this === b) { return true; } - return Buffer.compare(this, b) === 0; + return BufferCompare(this, b) === 0; }; -Buffer.prototype.inspect = function inspect() { - let str = ""; - const max = INSPECT_MAX_BYTES; - str = this.toString("hex", 0, max).replace(/(.{2})/g, "$1 ").trim(); - if (this.length > max) { - str += " ... "; - } - return ""; -}; +const SPACER_PATTERN = new SafeRegExp(/(.{2})/g); -if (customInspectSymbol) { - Buffer.prototype[customInspectSymbol] = Buffer.prototype.inspect; -} +Buffer.prototype[customInspectSymbol] = + Buffer.prototype.inspect = + function inspect() { + let str = ""; + const max = INSPECT_MAX_BYTES; + str = StringPrototypeTrim( + StringPrototypeReplace( + // deno-lint-ignore prefer-primordials + this.toString("hex", 0, max), + SPACER_PATTERN, + "$1 ", + ), + ); + if (this.length > max) { + str += " ... "; + } + return ""; + }; Buffer.prototype.compare = function compare( target, @@ -503,10 +598,14 @@ Buffer.prototype.compare = function compare( thisStart, thisEnd, ) { - if (isInstance(target, Uint8Array)) { - target = Buffer.from(target, target.offset, target.byteLength); + if (isUint8Array(target)) { + target = BufferFrom( + target, + TypedArrayPrototypeGetByteOffset(target), + TypedArrayPrototypeGetByteLength(target), + ); } - if (!Buffer.isBuffer(target)) { + if (!BufferIsBuffer(target)) { throw new codes.ERR_INVALID_ARG_TYPE( "target", ["Buffer", "Uint8Array"], @@ -563,8 +662,9 @@ Buffer.prototype.compare = function compare( } let x = thisEnd - thisStart; let y = end - start; - const len = Math.min(x, y); - const thisCopy = this.slice(thisStart, thisEnd); + const len = MathMin(x, y); + const thisCopy = TypedArrayPrototypeSlice(this, thisStart, thisEnd); + // deno-lint-ignore prefer-primordials const targetCopy = target.slice(start, end); for (let i = 0; i < len; ++i) { if (thisCopy[i] !== targetCopy[i]) { @@ -594,7 +694,8 @@ function bidirectionalIndexOf(buffer, val, byteOffset, encoding, dir) { byteOffset = -0x80000000; } byteOffset = +byteOffset; - if (Number.isNaN(byteOffset)) { + if (NumberIsNaN(byteOffset)) { + // deno-lint-ignore prefer-primordials byteOffset = dir ? 0 : (buffer.length || buffer.byteLength); } dir = !!dir; @@ -614,6 +715,7 @@ function bidirectionalIndexOf(buffer, val, byteOffset, encoding, dir) { if (ops === undefined) { throw new codes.ERR_UNKNOWN_ENCODING(encoding); } + // deno-lint-ignore prefer-primordials return ops.indexOf(buffer, val, byteOffset, dir); } @@ -630,6 +732,7 @@ function bidirectionalIndexOf(buffer, val, byteOffset, encoding, dir) { } Buffer.prototype.includes = function includes(val, byteOffset, encoding) { + // deno-lint-ignore prefer-primordials return this.indexOf(val, byteOffset, encoding) !== -1; }; @@ -649,7 +752,7 @@ Buffer.prototype.asciiSlice = function asciiSlice(offset, length) { if (offset === 0 && length === this.length) { return bytesToAscii(this); } else { - return bytesToAscii(this.slice(offset, length)); + return bytesToAscii(TypedArrayPrototypeSlice(this, offset, length)); } }; @@ -664,7 +767,9 @@ Buffer.prototype.base64Slice = function base64Slice( if (offset === 0 && length === this.length) { return forgivingBase64Encode(this); } else { - return forgivingBase64Encode(this.slice(offset, length)); + return forgivingBase64Encode( + TypedArrayPrototypeSlice(this, offset, length), + ); } }; @@ -683,7 +788,9 @@ Buffer.prototype.base64urlSlice = function base64urlSlice( if (offset === 0 && length === this.length) { return forgivingBase64UrlEncode(this); } else { - return forgivingBase64UrlEncode(this.slice(offset, length)); + return forgivingBase64UrlEncode( + TypedArrayPrototypeSlice(this, offset, length), + ); } }; @@ -728,7 +835,7 @@ Buffer.prototype.ucs2Slice = function ucs2Slice(offset, length) { if (offset === 0 && length === this.length) { return bytesToUtf16le(this); } else { - return bytesToUtf16le(this.slice(offset, length)); + return bytesToUtf16le(TypedArrayPrototypeSlice(this, offset, length)); } }; @@ -747,9 +854,9 @@ Buffer.prototype.utf8Slice = function utf8Slice(string, offset, length) { Buffer.prototype.utf8Write = function utf8Write(string, offset, length) { offset = offset || 0; - const maxLength = Math.min(length || Infinity, this.length - offset); + const maxLength = MathMin(length || Infinity, this.length - offset); const buf = offset || maxLength < this.length - ? this.subarray(offset, maxLength + offset) + ? TypedArrayPrototypeSubarray(this, offset, maxLength + offset) : this; return utf8Encoder.encodeInto(string, buf).written; }; @@ -801,7 +908,7 @@ Buffer.prototype.write = function write(string, offset, length, encoding) { Buffer.prototype.toJSON = function toJSON() { return { type: "Buffer", - data: Array.prototype.slice.call(this._arr || this, 0), + data: ArrayPrototypeSlice(this._arr || this, 0), }; }; function fromArrayBuffer(obj, byteOffset, length) { @@ -810,11 +917,12 @@ function fromArrayBuffer(obj, byteOffset, length) { byteOffset = 0; } else { byteOffset = +byteOffset; - if (Number.isNaN(byteOffset)) { + if (NumberIsNaN(byteOffset)) { byteOffset = 0; } } + // deno-lint-ignore prefer-primordials const maxLength = obj.byteLength - byteOffset; if (maxLength < 0) { @@ -836,7 +944,7 @@ function fromArrayBuffer(obj, byteOffset, length) { } const buffer = new Uint8Array(obj, byteOffset, length); - Object.setPrototypeOf(buffer, Buffer.prototype); + ObjectSetPrototypeOf(buffer, BufferPrototype); return buffer; } @@ -844,6 +952,7 @@ function _base64Slice(buf, start, end) { if (start === 0 && end === buf.length) { return forgivingBase64Encode(buf); } else { + // deno-lint-ignore prefer-primordials return forgivingBase64Encode(buf.slice(start, end)); } } @@ -852,9 +961,10 @@ const decoder = new TextDecoder(); function _utf8Slice(buf, start, end) { try { + // deno-lint-ignore prefer-primordials return decoder.decode(buf.slice(start, end)); } catch (err) { - if (err instanceof TypeError) { + if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, err)) { throw new NodeError("ERR_STRING_TOO_LONG", "String too long"); } throw err; @@ -863,9 +973,9 @@ function _utf8Slice(buf, start, end) { function _latin1Slice(buf, start, end) { let ret = ""; - end = Math.min(buf.length, end); + end = MathMin(buf.length, end); for (let i = start; i < end; ++i) { - ret += String.fromCharCode(buf[i]); + ret += StringFromCharCode(buf[i]); } return ret; } @@ -994,42 +1104,38 @@ Buffer.prototype.readUint32BE = Buffer.prototype.readUInt32BE = readUInt32BE; Buffer.prototype.readBigUint64LE = Buffer.prototype.readBigUInt64LE = - defineBigIntMethod( - function readBigUInt64LE(offset) { - offset = offset >>> 0; - validateNumber(offset, "offset"); - const first = this[offset]; - const last = this[offset + 7]; - if (first === void 0 || last === void 0) { - boundsError(offset, this.length - 8); - } - const lo = first + this[++offset] * 2 ** 8 + - this[++offset] * 2 ** 16 + - this[++offset] * 2 ** 24; - const hi = this[++offset] + this[++offset] * 2 ** 8 + - this[++offset] * 2 ** 16 + last * 2 ** 24; - return BigInt(lo) + (BigInt(hi) << BigInt(32)); - }, - ); + function readBigUInt64LE(offset) { + offset = offset >>> 0; + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 7]; + if (first === void 0 || last === void 0) { + boundsError(offset, this.length - 8); + } + const lo = first + this[++offset] * 2 ** 8 + + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 24; + const hi = this[++offset] + this[++offset] * 2 ** 8 + + this[++offset] * 2 ** 16 + last * 2 ** 24; + return BigInt(lo) + (BigInt(hi) << 32n); + }; Buffer.prototype.readBigUint64BE = Buffer.prototype.readBigUInt64BE = - defineBigIntMethod( - function readBigUInt64BE(offset) { - offset = offset >>> 0; - validateNumber(offset, "offset"); - const first = this[offset]; - const last = this[offset + 7]; - if (first === void 0 || last === void 0) { - boundsError(offset, this.length - 8); - } - const hi = first * 2 ** 24 + this[++offset] * 2 ** 16 + - this[++offset] * 2 ** 8 + this[++offset]; - const lo = this[++offset] * 2 ** 24 + this[++offset] * 2 ** 16 + - this[++offset] * 2 ** 8 + last; - return (BigInt(hi) << BigInt(32)) + BigInt(lo); - }, - ); + function readBigUInt64BE(offset) { + offset = offset >>> 0; + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 7]; + if (first === void 0 || last === void 0) { + boundsError(offset, this.length - 8); + } + const hi = first * 2 ** 24 + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 8 + this[++offset]; + const lo = this[++offset] * 2 ** 24 + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 8 + last; + return (BigInt(hi) << 32n) + BigInt(lo); + }; Buffer.prototype.readIntLE = function readIntLE( offset, @@ -1148,43 +1254,39 @@ Buffer.prototype.readInt32BE = function readInt32BE(offset = 0) { last; }; -Buffer.prototype.readBigInt64LE = defineBigIntMethod( - function readBigInt64LE(offset) { - offset = offset >>> 0; - validateNumber(offset, "offset"); - const first = this[offset]; - const last = this[offset + 7]; - if (first === void 0 || last === void 0) { - boundsError(offset, this.length - 8); - } - const val = this[offset + 4] + this[offset + 5] * 2 ** 8 + - this[offset + 6] * 2 ** 16 + (last << 24); - return (BigInt(val) << BigInt(32)) + - BigInt( - first + this[++offset] * 2 ** 8 + this[++offset] * 2 ** 16 + - this[++offset] * 2 ** 24, - ); - }, -); +Buffer.prototype.readBigInt64LE = function readBigInt64LE(offset) { + offset = offset >>> 0; + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 7]; + if (first === void 0 || last === void 0) { + boundsError(offset, this.length - 8); + } + const val = this[offset + 4] + this[offset + 5] * 2 ** 8 + + this[offset + 6] * 2 ** 16 + (last << 24); + return (BigInt(val) << 32n) + + BigInt( + first + this[++offset] * 2 ** 8 + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 24, + ); +}; -Buffer.prototype.readBigInt64BE = defineBigIntMethod( - function readBigInt64BE(offset) { - offset = offset >>> 0; - validateNumber(offset, "offset"); - const first = this[offset]; - const last = this[offset + 7]; - if (first === void 0 || last === void 0) { - boundsError(offset, this.length - 8); - } - const val = (first << 24) + this[++offset] * 2 ** 16 + - this[++offset] * 2 ** 8 + this[++offset]; - return (BigInt(val) << BigInt(32)) + - BigInt( - this[++offset] * 2 ** 24 + this[++offset] * 2 ** 16 + - this[++offset] * 2 ** 8 + last, - ); - }, -); +Buffer.prototype.readBigInt64BE = function readBigInt64BE(offset) { + offset = offset >>> 0; + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 7]; + if (first === void 0 || last === void 0) { + boundsError(offset, this.length - 8); + } + const val = (first << 24) + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 8 + this[++offset]; + return (BigInt(val) << 32n) + + BigInt( + this[++offset] * 2 ** 24 + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 8 + last, + ); +}; Buffer.prototype.readFloatLE = function readFloatLE(offset) { return bigEndian @@ -1293,7 +1395,7 @@ Buffer.prototype.writeUint32BE = function wrtBigUInt64LE(buf, value, offset, min, max) { checkIntBI(value, min, max, buf, offset, 7); - let lo = Number(value & BigInt(4294967295)); + let lo = Number(value & 4294967295n); buf[offset++] = lo; lo = lo >> 8; buf[offset++] = lo; @@ -1301,7 +1403,7 @@ function wrtBigUInt64LE(buf, value, offset, min, max) { buf[offset++] = lo; lo = lo >> 8; buf[offset++] = lo; - let hi = Number(value >> BigInt(32) & BigInt(4294967295)); + let hi = Number(value >> 32n & 4294967295n); buf[offset++] = hi; hi = hi >> 8; buf[offset++] = hi; @@ -1314,7 +1416,7 @@ function wrtBigUInt64LE(buf, value, offset, min, max) { function wrtBigUInt64BE(buf, value, offset, min, max) { checkIntBI(value, min, max, buf, offset, 7); - let lo = Number(value & BigInt(4294967295)); + let lo = Number(value & 4294967295n); buf[offset + 7] = lo; lo = lo >> 8; buf[offset + 6] = lo; @@ -1322,7 +1424,7 @@ function wrtBigUInt64BE(buf, value, offset, min, max) { buf[offset + 5] = lo; lo = lo >> 8; buf[offset + 4] = lo; - let hi = Number(value >> BigInt(32) & BigInt(4294967295)); + let hi = Number(value >> 32n & 4294967295n); buf[offset + 3] = hi; hi = hi >> 8; buf[offset + 2] = hi; @@ -1335,31 +1437,27 @@ function wrtBigUInt64BE(buf, value, offset, min, max) { Buffer.prototype.writeBigUint64LE = Buffer.prototype.writeBigUInt64LE = - defineBigIntMethod( - function writeBigUInt64LE(value, offset = 0) { - return wrtBigUInt64LE( - this, - value, - offset, - BigInt(0), - BigInt("0xffffffffffffffff"), - ); - }, - ); + function writeBigUInt64LE(value, offset = 0) { + return wrtBigUInt64LE( + this, + value, + offset, + 0n, + 0xffffffffffffffffn, + ); + }; Buffer.prototype.writeBigUint64BE = Buffer.prototype.writeBigUInt64BE = - defineBigIntMethod( - function writeBigUInt64BE(value, offset = 0) { - return wrtBigUInt64BE( - this, - value, - offset, - BigInt(0), - BigInt("0xffffffffffffffff"), - ); - }, - ); + function writeBigUInt64BE(value, offset = 0) { + return wrtBigUInt64BE( + this, + value, + offset, + 0n, + 0xffffffffffffffffn, + ); + }; Buffer.prototype.writeIntLE = function writeIntLE( value, @@ -1450,29 +1548,25 @@ Buffer.prototype.writeInt32BE = function writeInt32BE(value, offset = 0) { return writeU_Int32BE(this, value, offset, -0x80000000, 0x7fffffff); }; -Buffer.prototype.writeBigInt64LE = defineBigIntMethod( - function writeBigInt64LE(value, offset = 0) { - return wrtBigUInt64LE( - this, - value, - offset, - -BigInt("0x8000000000000000"), - BigInt("0x7fffffffffffffff"), - ); - }, -); +Buffer.prototype.writeBigInt64LE = function writeBigInt64LE(value, offset = 0) { + return wrtBigUInt64LE( + this, + value, + offset, + -0x8000000000000000n, + 0x7fffffffffffffffn, + ); +}; -Buffer.prototype.writeBigInt64BE = defineBigIntMethod( - function writeBigInt64BE(value, offset = 0) { - return wrtBigUInt64BE( - this, - value, - offset, - -BigInt("0x8000000000000000"), - BigInt("0x7fffffffffffffff"), - ); - }, -); +Buffer.prototype.writeBigInt64BE = function writeBigInt64BE(value, offset = 0) { + return wrtBigUInt64BE( + this, + value, + offset, + -0x8000000000000000n, + 0x7fffffffffffffffn, + ); +}; Buffer.prototype.writeFloatLE = function writeFloatLE( value, @@ -1600,14 +1694,12 @@ Buffer.prototype.copy = function copy( } const len = sourceEnd - sourceStart; - if ( - this === target && typeof Uint8Array.prototype.copyWithin === "function" - ) { - this.copyWithin(targetStart, sourceStart, sourceEnd); + if (this === target) { + TypedArrayPrototypeCopyWithin(this, targetStart, sourceStart, sourceEnd); } else { - Uint8Array.prototype.set.call( + TypedArrayPrototypeSet( target, - this.subarray(sourceStart, sourceEnd), + TypedArrayPrototypeSubarray(this, sourceStart, sourceEnd), targetStart, ); } @@ -1627,11 +1719,11 @@ Buffer.prototype.fill = function fill(val, start, end, encoding) { if (encoding !== void 0 && typeof encoding !== "string") { throw new TypeError("encoding must be a string"); } - if (typeof encoding === "string" && !Buffer.isEncoding(encoding)) { + if (typeof encoding === "string" && !BufferIsEncoding(encoding)) { throw new TypeError("Unknown encoding: " + encoding); } if (val.length === 1) { - const code = val.charCodeAt(0); + const code = StringPrototypeCharCodeAt(val, 0); if (encoding === "utf8" && code < 128 || encoding === "latin1") { val = code; } @@ -1658,7 +1750,7 @@ Buffer.prototype.fill = function fill(val, start, end, encoding) { this[i] = val; } } else { - const bytes = Buffer.isBuffer(val) ? val : Buffer.from(val, encoding); + const bytes = BufferIsBuffer(val) ? val : BufferFrom(val, encoding); const len = bytes.length; if (len === 0) { throw new codes.ERR_INVALID_ARG_VALUE( @@ -1685,7 +1777,7 @@ function checkIntBI(value, min, max, buf, offset, byteLength2) { const n = typeof min === "bigint" ? "n" : ""; let range; if (byteLength2 > 3) { - if (min === 0 || min === BigInt(0)) { + if (min === 0 || min === 0n) { range = `>= 0${n} and < 2${n} ** ${(byteLength2 + 1) * 8}${n}`; } else { range = `>= -(2${n} ** ${(byteLength2 + 1) * 8 - 1}${n}) and < 2 ** ${ @@ -1710,7 +1802,7 @@ function checkIntBI(value, min, max, buf, offset, byteLength2) { function blitBuffer(src, dst, offset, byteLength = Infinity) { const srcLength = src.length; // Establish the number of bytes to be written - const bytesToWrite = Math.min( + const bytesToWrite = MathMin( // If byte length is defined in the call, then it sets an upper bound, // otherwise it is Infinity and is never chosen. byteLength, @@ -1730,15 +1822,9 @@ function blitBuffer(src, dst, offset, byteLength = Infinity) { return bytesToWrite; } -function isInstance(obj, type) { - return obj instanceof type || - obj != null && obj.constructor != null && - obj.constructor.name != null && obj.constructor.name === type.name; -} - const hexSliceLookupTable = function () { const alphabet = "0123456789abcdef"; - const table = new Array(256); + const table = []; for (let i = 0; i < 16; ++i) { const i16 = i * 16; for (let j = 0; j < 16; ++j) { @@ -1748,14 +1834,6 @@ const hexSliceLookupTable = function () { return table; }(); -function defineBigIntMethod(fn) { - return typeof BigInt === "undefined" ? BufferBigIntNotDefined : fn; -} - -function BufferBigIntNotDefined() { - throw new Error("BigInt not supported"); -} - export function readUInt48LE(buf, offset = 0) { validateNumber(offset, "offset"); const first = buf[offset]; @@ -2079,10 +2157,10 @@ export function byteLengthUtf8(str) { function base64ByteLength(str, bytes) { // Handle padding - if (str.charCodeAt(bytes - 1) === 0x3D) { + if (StringPrototypeCharCodeAt(str, bytes - 1) === 0x3D) { bytes--; } - if (bytes > 1 && str.charCodeAt(bytes - 1) === 0x3D) { + if (bytes > 1 && StringPrototypeCharCodeAt(str, bytes - 1) === 0x3D) { bytes--; } @@ -2090,7 +2168,7 @@ function base64ByteLength(str, bytes) { return (bytes * 3) >>> 2; } -export const encodingsMap = Object.create(null); +export const encodingsMap = ObjectCreate(null); for (let i = 0; i < encodings.length; ++i) { encodingsMap[encodings[i]] = i; } @@ -2220,7 +2298,7 @@ export const encodingOps = { }; export function getEncodingOps(encoding) { - encoding = String(encoding).toLowerCase(); + encoding = StringPrototypeToLowerCase(String(encoding)); switch (encoding.length) { case 4: if (encoding === "utf8") return encodingOps.utf8; @@ -2260,6 +2338,14 @@ export function getEncodingOps(encoding) { } } +/** + * @param {Buffer} source + * @param {Buffer} target + * @param {number} targetStart + * @param {number} sourceStart + * @param {number} sourceEnd + * @returns {number} + */ export function _copyActual( source, target, @@ -2278,6 +2364,7 @@ export function _copyActual( } if (sourceStart !== 0 || sourceEnd < source.length) { + // deno-lint-ignore prefer-primordials source = new Uint8Array(source.buffer, source.byteOffset + sourceStart, nb); } @@ -2287,7 +2374,7 @@ export function _copyActual( } export function boundsError(value, length, type) { - if (Math.floor(value) !== value) { + if (MathFloor(value) !== value) { validateNumber(value, type); throw new codes.ERR_OUT_OF_RANGE(type || "offset", "an integer", value); } @@ -2310,7 +2397,7 @@ export function validateNumber(value, name, min = undefined, max) { if ( (min != null && value < min) || (max != null && value > max) || - ((min != null || max != null) && Number.isNaN(value)) + ((min != null || max != null) && NumberIsNaN(value)) ) { throw new codes.ERR_OUT_OF_RANGE( name, @@ -2344,11 +2431,11 @@ function checkInt(value, min, max, buf, offset, byteLength) { export function toInteger(n, defaultVal) { n = +n; if ( - !Number.isNaN(n) && - n >= Number.MIN_SAFE_INTEGER && - n <= Number.MAX_SAFE_INTEGER + !NumberIsNaN(n) && + n >= NumberMIN_SAFE_INTEGER && + n <= NumberMAX_SAFE_INTEGER ) { - return ((n % 1) === 0 ? n : Math.floor(n)); + return ((n % 1) === 0 ? n : MathFloor(n)); } return defaultVal; } @@ -2421,7 +2508,7 @@ export function writeU_Int48BE(buf, value, offset, min, max) { value = +value; checkInt(value, min, max, buf, offset, 5); - const newVal = Math.floor(value * 2 ** -32); + const newVal = MathFloor(value * 2 ** -32); buf[offset++] = newVal >>> 8; buf[offset++] = newVal; buf[offset + 3] = value; @@ -2439,7 +2526,7 @@ export function writeU_Int40BE(buf, value, offset, min, max) { value = +value; checkInt(value, min, max, buf, offset, 4); - buf[offset++] = Math.floor(value * 2 ** -32); + buf[offset++] = MathFloor(value * 2 ** -32); buf[offset + 3] = value; value = value >>> 8; buf[offset + 2] = value; @@ -2482,12 +2569,12 @@ export function validateOffset( value, name, min = 0, - max = Number.MAX_SAFE_INTEGER, + max = NumberMAX_SAFE_INTEGER, ) { if (typeof value !== "number") { throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value); } - if (!Number.isInteger(value)) { + if (!NumberIsInteger(value)) { throw new codes.ERR_OUT_OF_RANGE(name, "an integer", value); } if (value < min || value > max) { @@ -2500,7 +2587,7 @@ export function writeU_Int48LE(buf, value, offset, min, max) { value = +value; checkInt(value, min, max, buf, offset, 5); - const newVal = Math.floor(value * 2 ** -32); + const newVal = MathFloor(value * 2 ** -32); buf[offset++] = value; value = value >>> 8; buf[offset++] = value; @@ -2526,7 +2613,7 @@ export function writeU_Int40LE(buf, value, offset, min, max) { buf[offset++] = value; value = value >>> 8; buf[offset++] = value; - buf[offset++] = Math.floor(newVal * 2 ** -32); + buf[offset++] = MathFloor(newVal * 2 ** -32); return offset; } @@ -2560,14 +2647,14 @@ export function writeU_Int24LE(buf, value, offset, min, max) { export function isUtf8(input) { if (isTypedArray(input)) { - if (input.buffer.detached) { + if (isDetachedBuffer(TypedArrayPrototypeGetBuffer(input))) { throw new ERR_INVALID_STATE("Cannot validate on a detached buffer"); } return op_is_utf8(input); } if (isAnyArrayBuffer(input)) { - if (input.detached) { + if (isDetachedBuffer(input)) { throw new ERR_INVALID_STATE("Cannot validate on a detached buffer"); } return op_is_utf8(new Uint8Array(input)); @@ -2582,14 +2669,14 @@ export function isUtf8(input) { export function isAscii(input) { if (isTypedArray(input)) { - if (input.buffer.detached) { + if (isDetachedBuffer(TypedArrayPrototypeGetBuffer(input))) { throw new ERR_INVALID_STATE("Cannot validate on a detached buffer"); } return op_is_ascii(input); } if (isAnyArrayBuffer(input)) { - if (input.detached) { + if (isDetachedBuffer(input)) { throw new ERR_INVALID_STATE("Cannot validate on a detached buffer"); } return op_is_ascii(new Uint8Array(input)); @@ -2636,7 +2723,7 @@ export function transcode(source, fromEnco, toEnco) { const result = op_transcode(new Uint8Array(source), fromEnco, toEnco); return Buffer.from(result, toEnco); } catch (err) { - if (err.message.includes("Unable to transcode Buffer")) { + if (StringPrototypeIncludes(err.message, "Unable to transcode Buffer")) { throw illegalArgumentError; } else { throw err; -- cgit v1.2.3 From 21fa953f320c66a897822c4c731b2fae5f07c78b Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 16 Oct 2024 14:27:28 +0530 Subject: fix(ext/node): timingSafeEqual account for AB byteOffset (#26292) Fixes https://github.com/denoland/deno/issues/26276 --- .../polyfills/internal_binding/_timingSafeEqual.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/internal_binding/_timingSafeEqual.ts b/ext/node/polyfills/internal_binding/_timingSafeEqual.ts index ff141fdbf..559b7685b 100644 --- a/ext/node/polyfills/internal_binding/_timingSafeEqual.ts +++ b/ext/node/polyfills/internal_binding/_timingSafeEqual.ts @@ -5,10 +5,11 @@ import { Buffer } from "node:buffer"; -function assert(cond) { - if (!cond) { - throw new Error("assertion failed"); +function toDataView(ab: ArrayBufferLike | ArrayBufferView): DataView { + if (ArrayBuffer.isView(ab)) { + return new DataView(ab.buffer, ab.byteOffset, ab.byteLength); } + return new DataView(ab); } /** Compare to array buffers or data views in a way that timing based attacks @@ -21,13 +22,11 @@ function stdTimingSafeEqual( return false; } if (!(a instanceof DataView)) { - a = new DataView(ArrayBuffer.isView(a) ? a.buffer : a); + a = toDataView(a); } if (!(b instanceof DataView)) { - b = new DataView(ArrayBuffer.isView(b) ? b.buffer : b); + b = toDataView(b); } - assert(a instanceof DataView); - assert(b instanceof DataView); const length = a.byteLength; let out = 0; let i = -1; @@ -41,7 +40,11 @@ export const timingSafeEqual = ( a: Buffer | DataView | ArrayBuffer, b: Buffer | DataView | ArrayBuffer, ): boolean => { - if (a instanceof Buffer) a = new DataView(a.buffer); - if (a instanceof Buffer) b = new DataView(a.buffer); + if (a instanceof Buffer) { + a = new DataView(a.buffer, a.byteOffset, a.byteLength); + } + if (b instanceof Buffer) { + b = new DataView(b.buffer, b.byteOffset, b.byteLength); + } return stdTimingSafeEqual(a, b); }; -- cgit v1.2.3 From d59599fc187c559ee231882773e1c5a2b932fc3d Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Wed, 16 Oct 2024 20:58:44 +0900 Subject: fix(ext/node): fix dns.lookup result ordering (#26264) partially unblocks #25470 This PR aligns the resolution of `localhost` hostname to Node.js behavior. In Node.js `dns.lookup("localhost", (_, addr) => console.log(addr))` prints ipv6 address `::1`, but it prints ipv4 address `127.0.0.1` in Deno. That difference causes some errors in the work of enabling `createConnection` option in `http.request` (#25470). This PR fixes the issue by aligning `dns.lookup` behavior to Node.js. This PR also changes the following behaviors (resolving TODOs): - `http.createServer` now listens on ipv6 address `[::]` by default on linux/mac - `net.createServer` now listens on ipv6 address `[::]` by default on linux/mac These changes are also alignments to Node.js behaviors. --- ext/node/polyfills/http.ts | 6 +++--- ext/node/polyfills/internal/dns/utils.ts | 14 ++----------- ext/node/polyfills/internal_binding/cares_wrap.ts | 13 +++++++++--- ext/node/polyfills/net.ts | 24 +++++++---------------- 4 files changed, 22 insertions(+), 35 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/http.ts b/ext/node/polyfills/http.ts index f3f6f86ed..349caeea6 100644 --- a/ext/node/polyfills/http.ts +++ b/ext/node/polyfills/http.ts @@ -50,6 +50,7 @@ import { urlToHttpOptions } from "ext:deno_node/internal/url.ts"; import { kEmptyObject } from "ext:deno_node/internal/util.mjs"; import { constants, TCP } from "ext:deno_node/internal_binding/tcp_wrap.ts"; import { notImplemented, warnNotImplemented } from "ext:deno_node/_utils.ts"; +import { isWindows } from "ext:deno_node/_util/os.ts"; import { connResetException, ERR_HTTP_HEADERS_SENT, @@ -1586,9 +1587,8 @@ export class ServerImpl extends EventEmitter { port = options.port | 0; } - // TODO(bnoordhuis) Node prefers [::] when host is omitted, - // we on the other hand default to 0.0.0.0. - let hostname = options.host ?? "0.0.0.0"; + // Use 0.0.0.0 for Windows, and [::] for other platforms. + let hostname = options.host ?? (isWindows ? "0.0.0.0" : "[::]"); if (hostname == "localhost") { hostname = "127.0.0.1"; } diff --git a/ext/node/polyfills/internal/dns/utils.ts b/ext/node/polyfills/internal/dns/utils.ts index 226fce93d..1e0c3d9ed 100644 --- a/ext/node/polyfills/internal/dns/utils.ts +++ b/ext/node/polyfills/internal/dns/utils.ts @@ -416,20 +416,10 @@ export function emitInvalidHostnameWarning(hostname: string) { ); } -let dnsOrder = getOptionValue("--dns-result-order") || "ipv4first"; +let dnsOrder = getOptionValue("--dns-result-order") || "verbatim"; export function getDefaultVerbatim() { - switch (dnsOrder) { - case "verbatim": { - return true; - } - case "ipv4first": { - return false; - } - default: { - return false; - } - } + return dnsOrder !== "ipv4first"; } /** diff --git a/ext/node/polyfills/internal_binding/cares_wrap.ts b/ext/node/polyfills/internal_binding/cares_wrap.ts index 6feb7faf0..cbfea40b2 100644 --- a/ext/node/polyfills/internal_binding/cares_wrap.ts +++ b/ext/node/polyfills/internal_binding/cares_wrap.ts @@ -75,11 +75,18 @@ export function getaddrinfo( const recordTypes: ("A" | "AAAA")[] = []; - if (family === 0 || family === 4) { + if (family === 6) { + recordTypes.push("AAAA"); + } else if (family === 4) { recordTypes.push("A"); - } - if (family === 0 || family === 6) { + } else if (family === 0 && hostname === "localhost") { + // Ipv6 is preferred over Ipv4 for localhost recordTypes.push("AAAA"); + recordTypes.push("A"); + } else if (family === 0) { + // Only get Ipv4 addresses for the other hostnames + // This simulates what `getaddrinfo` does when the family is not specified + recordTypes.push("A"); } (async () => { diff --git a/ext/node/polyfills/net.ts b/ext/node/polyfills/net.ts index 48e1d0de8..35d273be9 100644 --- a/ext/node/polyfills/net.ts +++ b/ext/node/polyfills/net.ts @@ -1871,23 +1871,13 @@ function _setupListenHandle( // Try to bind to the unspecified IPv6 address, see if IPv6 is available if (!address && typeof fd !== "number") { - // TODO(@bartlomieju): differs from Node which tries to bind to IPv6 first - // when no address is provided. - // - // Forcing IPv4 as a workaround for Deno not aligning with Node on - // implicit binding on Windows. - // - // REF: https://github.com/denoland/deno/issues/10762 - // rval = _createServerHandle(DEFAULT_IPV6_ADDR, port, 6, fd, flags); - - // if (typeof rval === "number") { - // rval = null; - address = DEFAULT_IPV4_ADDR; - addressType = 4; - // } else { - // address = DEFAULT_IPV6_ADDR; - // addressType = 6; - // } + if (isWindows) { + address = DEFAULT_IPV4_ADDR; + addressType = 4; + } else { + address = DEFAULT_IPV6_ADDR; + addressType = 6; + } } if (rval === null) { -- cgit v1.2.3 From f7dba52133904a9e4ca0468e2c98894992ea2e42 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:25:25 -0700 Subject: fix(child_process): map node `--no-warnings` flag to `--quiet` (#26288) Closes https://github.com/denoland/deno/issues/25899 --- ext/node/polyfills/child_process.ts | 2 ++ ext/node/polyfills/internal/child_process.ts | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/child_process.ts b/ext/node/polyfills/child_process.ts index c37dfc410..eda718ff3 100644 --- a/ext/node/polyfills/child_process.ts +++ b/ext/node/polyfills/child_process.ts @@ -132,6 +132,8 @@ export function fork( rm = 2; } execArgv.splice(index, rm); + } else if (flag.startsWith("--no-warnings")) { + execArgv[index] = "--quiet"; } else { index++; } diff --git a/ext/node/polyfills/internal/child_process.ts b/ext/node/polyfills/internal/child_process.ts index 6f209b719..65d825fd2 100644 --- a/ext/node/polyfills/internal/child_process.ts +++ b/ext/node/polyfills/internal/child_process.ts @@ -1191,8 +1191,12 @@ function toDenoArgs(args: string[]): string[] { } if (flagInfo === undefined) { - // Not a known flag that expects a value. Just copy it to the output. - denoArgs.push(arg); + if (arg === "--no-warnings") { + denoArgs.push("--quiet"); + } else { + // Not a known flag that expects a value. Just copy it to the output. + denoArgs.push(arg); + } continue; } -- cgit v1.2.3 From 458d6278d2835896018086da773fd72b7af8ed66 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Wed, 16 Oct 2024 17:42:15 -0700 Subject: fix(node/http): normalize header names in `ServerResponse` (#26339) Fixes https://github.com/denoland/deno/issues/26115. We weren't normalizing the headers to lower case, so code that attempted to delete the `Content-Length` header (but used a different case) wasn't actually removing the header. --- ext/node/polyfills/http.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/http.ts b/ext/node/polyfills/http.ts index 349caeea6..e117a0ec2 100644 --- a/ext/node/polyfills/http.ts +++ b/ext/node/polyfills/http.ts @@ -72,7 +72,7 @@ import { STATUS_CODES } from "node:_http_server"; import { methods as METHODS } from "node:_http_common"; const { internalRidSymbol } = core; -const { ArrayIsArray } = primordials; +const { ArrayIsArray, StringPrototypeToLowerCase } = primordials; type Chunk = string | Buffer | Uint8Array; @@ -1270,20 +1270,21 @@ export class ServerResponse extends NodeWritable { if (Array.isArray(value)) { this.#hasNonStringHeaders = true; } - this.#headers[name] = value; + this.#headers[StringPrototypeToLowerCase(name)] = value; return this; } appendHeader(name: string, value: string | string[]) { - if (this.#headers[name] === undefined) { + const key = StringPrototypeToLowerCase(name); + if (this.#headers[key] === undefined) { if (Array.isArray(value)) this.#hasNonStringHeaders = true; - this.#headers[name] = value; + this.#headers[key] = value; } else { this.#hasNonStringHeaders = true; - if (!Array.isArray(this.#headers[name])) { - this.#headers[name] = [this.#headers[name]]; + if (!Array.isArray(this.#headers[key])) { + this.#headers[key] = [this.#headers[key]]; } - const header = this.#headers[name]; + const header = this.#headers[key]; if (Array.isArray(value)) { header.push(...value); } else { @@ -1294,10 +1295,10 @@ export class ServerResponse extends NodeWritable { } getHeader(name: string) { - return this.#headers[name]; + return this.#headers[StringPrototypeToLowerCase(name)]; } removeHeader(name: string) { - delete this.#headers[name]; + delete this.#headers[StringPrototypeToLowerCase(name)]; } getHeaderNames() { return Object.keys(this.#headers); -- cgit v1.2.3 From 167f674c7cbb9632000c1feb0b747ba098b01c12 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Wed, 16 Oct 2024 19:58:11 -0700 Subject: fix: don't warn on ignored signals on windows (#26332) Closes #26183. The warnings are super noisy and not actionable for the user --- ext/node/polyfills/process.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/process.ts b/ext/node/polyfills/process.ts index 3dc6ce61a..2605fa6d1 100644 --- a/ext/node/polyfills/process.ts +++ b/ext/node/polyfills/process.ts @@ -520,9 +520,7 @@ Process.prototype.on = function ( } else if ( event !== "SIGBREAK" && event !== "SIGINT" && Deno.build.os === "windows" ) { - // Ignores all signals except SIGBREAK and SIGINT on windows. - // deno-lint-ignore no-console - console.warn(`Ignoring signal "${event}" on Windows`); + // TODO(#26331): Ignores all signals except SIGBREAK and SIGINT on windows. } else { EventEmitter.prototype.on.call(this, event, listener); Deno.addSignalListener(event as Deno.Signal, listener); -- cgit v1.2.3 From a61ba3c6995bef58f508a34e537932284692c294 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Wed, 16 Oct 2024 20:56:57 -0700 Subject: fix(net): don't try to set nodelay on upgrade streams (#26342) Fixes https://github.com/denoland/deno/issues/26341. We try to call `op_set_nodelay` on an `UpgradeStream`, which doesn't support that operation. --- ext/node/polyfills/http.ts | 4 ++-- ext/node/polyfills/internal_binding/tcp_wrap.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/http.ts b/ext/node/polyfills/http.ts index e117a0ec2..20bef3009 100644 --- a/ext/node/polyfills/http.ts +++ b/ext/node/polyfills/http.ts @@ -67,7 +67,7 @@ import { headersEntries } from "ext:deno_fetch/20_headers.js"; import { timerId } from "ext:deno_web/03_abort_signal.js"; import { clearTimeout as webClearTimeout } from "ext:deno_web/02_timers.js"; import { resourceForReadableStream } from "ext:deno_web/06_streams.js"; -import { TcpConn } from "ext:deno_net/01_net.js"; +import { UpgradedConn } from "ext:deno_net/01_net.js"; import { STATUS_CODES } from "node:_http_server"; import { methods as METHODS } from "node:_http_common"; @@ -517,7 +517,7 @@ class ClientRequest extends OutgoingMessage { ); assert(typeof res.remoteAddrIp !== "undefined"); assert(typeof res.remoteAddrIp !== "undefined"); - const conn = new TcpConn( + const conn = new UpgradedConn( upgradeRid, { transport: "tcp", diff --git a/ext/node/polyfills/internal_binding/tcp_wrap.ts b/ext/node/polyfills/internal_binding/tcp_wrap.ts index 1321cc627..2856f808a 100644 --- a/ext/node/polyfills/internal_binding/tcp_wrap.ts +++ b/ext/node/polyfills/internal_binding/tcp_wrap.ts @@ -300,7 +300,9 @@ export class TCP extends ConnectionWrap { * @return An error status code. */ setNoDelay(noDelay: boolean): number { - this[kStreamBaseField].setNoDelay(noDelay); + if ("setNoDelay" in this[kStreamBaseField]) { + this[kStreamBaseField].setNoDelay(noDelay); + } return 0; } -- cgit v1.2.3 From 9fde5cb5e045551fe344b3f60370744eea30ccb4 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:51:15 -0700 Subject: fix(node/fs): copyFile with `COPYFILE_EXCL` should not throw if the destination doesn't exist (#26360) Fixes #26313. We were checking for the NotFound error, but still calling the callback with the error / throwing. --- ext/node/polyfills/_fs/_fs_copy.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/_fs/_fs_copy.ts b/ext/node/polyfills/_fs/_fs_copy.ts index 2f8ddf4fc..0434bff4d 100644 --- a/ext/node/polyfills/_fs/_fs_copy.ts +++ b/ext/node/polyfills/_fs/_fs_copy.ts @@ -53,8 +53,9 @@ export function copyFile( }, (e) => { if (e instanceof Deno.errors.NotFound) { Deno.copyFile(srcStr, destStr).then(() => cb(null), cb); + } else { + cb(e); } - cb(e); }); } else { Deno.copyFile(srcStr, destStr).then(() => cb(null), cb); @@ -83,8 +84,9 @@ export function copyFileSync( } catch (e) { if (e instanceof Deno.errors.NotFound) { Deno.copyFileSync(srcStr, destStr); + } else { + throw e; } - throw e; } } else { Deno.copyFileSync(srcStr, destStr); -- cgit v1.2.3 From 8f3eb9d0e7bbbfa67058c67c5e5c1484dee1ef9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 17 Oct 2024 23:57:05 +0100 Subject: fix(ext/node): add null check for kStreamBaseField (#26368) It's not guaranteed that `kStreamBaseField` is not undefined, so added a check for it. Closes https://github.com/denoland/deno/issues/26363 --- ext/node/polyfills/internal_binding/tcp_wrap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/internal_binding/tcp_wrap.ts b/ext/node/polyfills/internal_binding/tcp_wrap.ts index 2856f808a..d9f1c5356 100644 --- a/ext/node/polyfills/internal_binding/tcp_wrap.ts +++ b/ext/node/polyfills/internal_binding/tcp_wrap.ts @@ -300,7 +300,7 @@ export class TCP extends ConnectionWrap { * @return An error status code. */ setNoDelay(noDelay: boolean): number { - if ("setNoDelay" in this[kStreamBaseField]) { + if (this[kStreamBaseField] && "setNoDelay" in this[kStreamBaseField]) { this[kStreamBaseField].setNoDelay(noDelay); } return 0; -- cgit v1.2.3 From 1bccf45ecb8b5ce2aa685d650c6654bf6c25e605 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Fri, 18 Oct 2024 10:11:06 -0700 Subject: fix(ext/node): properly map reparse point error in readlink (#26375) --- ext/node/polyfills/_fs/_fs_readlink.ts | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/_fs/_fs_readlink.ts b/ext/node/polyfills/_fs/_fs_readlink.ts index 5f2312798..08bea843f 100644 --- a/ext/node/polyfills/_fs/_fs_readlink.ts +++ b/ext/node/polyfills/_fs/_fs_readlink.ts @@ -4,13 +4,10 @@ // deno-lint-ignore-file prefer-primordials import { TextEncoder } from "ext:deno_web/08_text_encoding.js"; -import { - intoCallbackAPIWithIntercept, - MaybeEmpty, - notImplemented, -} from "ext:deno_node/_utils.ts"; +import { MaybeEmpty, notImplemented } from "ext:deno_node/_utils.ts"; import { pathFromURL } from "ext:deno_web/00_infra.js"; import { promisify } from "ext:deno_node/internal/util.mjs"; +import { denoErrorToNodeError } from "ext:deno_node/internal/errors.ts"; type ReadlinkCallback = ( err: MaybeEmpty, @@ -69,12 +66,17 @@ export function readlink( const encoding = getEncoding(optOrCallback); - intoCallbackAPIWithIntercept( - Deno.readLink, - (data: string): string | Uint8Array => maybeEncode(data, encoding), - cb, - path, - ); + Deno.readLink(path).then((data: string) => { + const res = maybeEncode(data, encoding); + if (cb) cb(null, res); + }, (err: Error) => { + if (cb) { + (cb as (e: Error) => void)(denoErrorToNodeError(err, { + syscall: "readlink", + path, + })); + } + }); } export const readlinkPromise = promisify(readlink) as ( @@ -88,5 +90,12 @@ export function readlinkSync( ): string | Uint8Array { path = path instanceof URL ? pathFromURL(path) : path; - return maybeEncode(Deno.readLinkSync(path), getEncoding(opt)); + try { + return maybeEncode(Deno.readLinkSync(path), getEncoding(opt)); + } catch (error) { + throw denoErrorToNodeError(error, { + syscall: "readlink", + path, + }); + } } -- cgit v1.2.3 From d48434e91fcb2945f103521fde782adad5742c63 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:39:32 -0700 Subject: fix(ext/node): stub HTTPParser internal binding (#26401) Fixes https://github.com/denoland/deno/issues/26394. --- ext/node/polyfills/internal_binding/http_parser.ts | 159 +++++++++++++++++++++ ext/node/polyfills/internal_binding/mod.ts | 3 +- 2 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 ext/node/polyfills/internal_binding/http_parser.ts (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/internal_binding/http_parser.ts b/ext/node/polyfills/internal_binding/http_parser.ts new file mode 100644 index 000000000..ca4f896e2 --- /dev/null +++ b/ext/node/polyfills/internal_binding/http_parser.ts @@ -0,0 +1,159 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { primordials } from "ext:core/mod.js"; +import { AsyncWrap } from "ext:deno_node/internal_binding/async_wrap.ts"; + +const { + ObjectDefineProperty, + ObjectEntries, + ObjectSetPrototypeOf, + SafeArrayIterator, +} = primordials; + +export const methods = [ + "DELETE", + "GET", + "HEAD", + "POST", + "PUT", + "CONNECT", + "OPTIONS", + "TRACE", + "COPY", + "LOCK", + "MKCOL", + "MOVE", + "PROPFIND", + "PROPPATCH", + "SEARCH", + "UNLOCK", + "BIND", + "REBIND", + "UNBIND", + "ACL", + "REPORT", + "MKACTIVITY", + "CHECKOUT", + "MERGE", + "M-SEARCH", + "NOTIFY", + "SUBSCRIBE", + "UNSUBSCRIBE", + "PATCH", + "PURGE", + "MKCALENDAR", + "LINK", + "UNLINK", + "SOURCE", + "QUERY", +]; + +export const allMethods = [ + "DELETE", + "GET", + "HEAD", + "POST", + "PUT", + "CONNECT", + "OPTIONS", + "TRACE", + "COPY", + "LOCK", + "MKCOL", + "MOVE", + "PROPFIND", + "PROPPATCH", + "SEARCH", + "UNLOCK", + "BIND", + "REBIND", + "UNBIND", + "ACL", + "REPORT", + "MKACTIVITY", + "CHECKOUT", + "MERGE", + "M-SEARCH", + "NOTIFY", + "SUBSCRIBE", + "UNSUBSCRIBE", + "PATCH", + "PURGE", + "MKCALENDAR", + "LINK", + "UNLINK", + "SOURCE", + "PRI", + "DESCRIBE", + "ANNOUNCE", + "SETUP", + "PLAY", + "PAUSE", + "TEARDOWN", + "GET_PARAMETER", + "SET_PARAMETER", + "REDIRECT", + "RECORD", + "FLUSH", + "QUERY", +]; + +export function HTTPParser() { +} + +ObjectSetPrototypeOf(HTTPParser.prototype, AsyncWrap.prototype); + +function defineProps(obj: object, props: Record) { + for (const entry of new SafeArrayIterator(ObjectEntries(props))) { + ObjectDefineProperty(obj, entry[0], { + value: entry[1], + enumerable: true, + writable: true, + configurable: true, + }); + } +} + +defineProps(HTTPParser, { + REQUEST: 1, + RESPONSE: 2, + kOnMessageBegin: 0, + kOnHeaders: 1, + kOnHeadersComplete: 2, + kOnBody: 3, + kOnMessageComplete: 4, + kOnExecute: 5, + kOnTimeout: 6, + kLenientNone: 0, + kLenientHeaders: 1, + kLenientChunkedLength: 2, + kLenientKeepAlive: 4, + kLenientTransferEncoding: 8, + kLenientVersion: 16, + kLenientDataAfterClose: 32, + kLenientOptionalLFAfterCR: 64, + kLenientOptionalCRLFAfterChunk: 128, + kLenientOptionalCRBeforeLF: 256, + kLenientSpacesAfterChunkSize: 512, + kLenientAll: 1023, +}); diff --git a/ext/node/polyfills/internal_binding/mod.ts b/ext/node/polyfills/internal_binding/mod.ts index f2d7f55bc..ebbfc629f 100644 --- a/ext/node/polyfills/internal_binding/mod.ts +++ b/ext/node/polyfills/internal_binding/mod.ts @@ -17,6 +17,7 @@ import * as types from "ext:deno_node/internal_binding/types.ts"; import * as udpWrap from "ext:deno_node/internal_binding/udp_wrap.ts"; import * as util from "ext:deno_node/internal_binding/util.ts"; import * as uv from "ext:deno_node/internal_binding/uv.ts"; +import * as httpParser from "ext:deno_node/internal_binding/http_parser.ts"; const modules = { "async_wrap": asyncWrap, @@ -32,7 +33,7 @@ const modules = { "fs_dir": {}, "fs_event_wrap": {}, "heap_utils": {}, - "http_parser": {}, + "http_parser": httpParser, icu: {}, inspector: {}, "js_stream": {}, -- cgit v1.2.3 From 0710af034ffc7737cbfa689ff1826d9c1537f6da Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sat, 19 Oct 2024 08:42:59 +0530 Subject: perf: avoid multiple calls to runMicrotask (#26378) Improves HTTP throughput by 8-9k rps on Linux: this patch ``` Requests/sec: 145001.69 Transfer/sec: 20.74MB ``` main ``` Requests/sec: 137866.61 Transfer/sec: 19.72MB ``` The improvements comes from the reduced number of calls to `op_run_microtask` per request. Returning `true` from a macrotask callback already calls `op_run_microtask` so the extra call was redundant. Here's `--strace-ops` output for a single request: main ``` [ 4.667] op_http_wait : CompletedAsync Async [ 4.667] op_run_microtasks : Dispatched Slow [ 4.668] op_http_try_wait : Dispatched Slow [ 4.668] op_http_try_wait : Completed Slow [ 4.668] op_http_wait : Dispatched Async [ 4.668] op_http_set_response_header : Dispatched Slow [ 4.668] op_http_set_response_header : Completed Slow [ 4.669] op_http_set_response_body_text : Dispatched Slow [ 4.669] op_http_set_response_body_text : Completed Slow [ 4.669] op_run_microtasks : Completed Slow [ 4.669] op_has_tick_scheduled : Dispatched Slow [ 4.669] op_has_tick_scheduled : Completed Slow [ 4.669] op_run_microtasks : Dispatched Slow [ 4.669] op_run_microtasks : Completed Slow [ 4.669] op_run_microtasks : Dispatched Slow [ 4.669] op_run_microtasks : Completed Slow ``` this pr ``` [ 3.726] op_http_wait : CompletedAsync Async [ 3.727] op_run_microtasks : Dispatched Slow [ 3.727] op_http_try_wait : Dispatched Slow [ 3.727] op_http_try_wait : Completed Slow [ 3.727] op_http_wait : Dispatched Async [ 3.727] op_http_set_response_header : Dispatched Slow [ 3.728] op_http_set_response_header : Completed Slow [ 3.728] op_http_set_response_body_text : Dispatched Slow [ 3.728] op_http_set_response_body_text : Completed Slow [ 3.728] op_run_microtasks : Completed Slow [ 3.728] op_run_microtasks : Dispatched Slow [ 3.728] op_run_microtasks : Completed Slow ``` --- ext/node/polyfills/_next_tick.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/_next_tick.ts b/ext/node/polyfills/_next_tick.ts index 5ee27728d..62470c564 100644 --- a/ext/node/polyfills/_next_tick.ts +++ b/ext/node/polyfills/_next_tick.ts @@ -87,8 +87,7 @@ export function runNextTicks() { // runMicrotasks(); // if (!hasTickScheduled() && !hasRejectionToWarn()) // return; - if (!core.hasTickScheduled()) { - core.runMicrotasks(); + if (queue.isEmpty() || !core.hasTickScheduled()) { return true; } -- cgit v1.2.3 From afb33b3c2597c9ec943f71218b236486fbc86e23 Mon Sep 17 00:00:00 2001 From: jiang1997 Date: Mon, 21 Oct 2024 15:50:53 +0800 Subject: fix(ext/node): use primordials in `ext/node/polyfills/https.ts` (#26323) Towards https://github.com/denoland/deno/issues/24236 --- ext/node/polyfills/https.ts | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/https.ts b/ext/node/polyfills/https.ts index f60c5e471..dd24cf048 100644 --- a/ext/node/polyfills/https.ts +++ b/ext/node/polyfills/https.ts @@ -1,9 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright Joyent and Node contributors. All rights reserved. MIT license. -// TODO(petamoriken): enable prefer-primordials for node polyfills -// deno-lint-ignore-file prefer-primordials - import { notImplemented } from "ext:deno_node/_utils.ts"; import { urlToHttpOptions } from "ext:deno_node/internal/url.ts"; import { @@ -17,6 +14,14 @@ import { type ServerHandler, ServerImpl as HttpServer } from "node:http"; import { validateObject } from "ext:deno_node/internal/validators.mjs"; import { kEmptyObject } from "ext:deno_node/internal/util.mjs"; import { Buffer } from "node:buffer"; +import { primordials } from "ext:core/mod.js"; +const { + ArrayPrototypeShift, + ArrayPrototypeUnshift, + ArrayIsArray, + ObjectPrototypeIsPrototypeOf, + ObjectAssign, +} = primordials; export class Server extends HttpServer { constructor(opts, requestListener?: ServerHandler) { @@ -29,11 +34,11 @@ export class Server extends HttpServer { validateObject(opts, "options"); } - if (opts.cert && Array.isArray(opts.cert)) { + if (opts.cert && ArrayIsArray(opts.cert)) { notImplemented("https.Server.opts.cert array type"); } - if (opts.key && Array.isArray(opts.key)) { + if (opts.key && ArrayIsArray(opts.key)) { notImplemented("https.Server.opts.key array type"); } @@ -42,10 +47,12 @@ export class Server extends HttpServer { _additionalServeOptions() { return { - cert: this._opts.cert instanceof Buffer + cert: ObjectPrototypeIsPrototypeOf(Buffer, this._opts.cert) + // deno-lint-ignore prefer-primordials ? this._opts.cert.toString() : this._opts.cert, - key: this._opts.key instanceof Buffer + key: ObjectPrototypeIsPrototypeOf(Buffer, this._opts.key) + // deno-lint-ignore prefer-primordials ? this._opts.key.toString() : this._opts.key, }; @@ -159,18 +166,18 @@ export function request(...args: any[]) { let options = {}; if (typeof args[0] === "string") { - const urlStr = args.shift(); + const urlStr = ArrayPrototypeShift(args); options = urlToHttpOptions(new URL(urlStr)); - } else if (args[0] instanceof URL) { - options = urlToHttpOptions(args.shift()); + } else if (ObjectPrototypeIsPrototypeOf(URL, args[0])) { + options = urlToHttpOptions(ArrayPrototypeShift(args)); } if (args[0] && typeof args[0] !== "function") { - Object.assign(options, args.shift()); + ObjectAssign(options, ArrayPrototypeShift(args)); } options._defaultAgent = globalAgent; - args.unshift(options); + ArrayPrototypeUnshift(args, options); return new HttpsClientRequest(args[0], args[1]); } -- cgit v1.2.3 From 92ed4d38dbef98b9353d6dd6d96abb400be56f9f Mon Sep 17 00:00:00 2001 From: Satya Rohith Date: Wed, 23 Oct 2024 13:17:43 +0530 Subject: fix(node:tls): set TLSSocket.alpnProtocol for client connections (#26476) Towards https://github.com/denoland/deno/issues/26127 --- ext/node/polyfills/_tls_wrap.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/_tls_wrap.ts b/ext/node/polyfills/_tls_wrap.ts index a614b45df..e36fc637e 100644 --- a/ext/node/polyfills/_tls_wrap.ts +++ b/ext/node/polyfills/_tls_wrap.ts @@ -68,6 +68,7 @@ export class TLSSocket extends net.Socket { secureConnecting: boolean; _SNICallback: any; servername: string | null; + alpnProtocol: string | boolean | null; alpnProtocols: string[] | null; authorized: boolean; authorizationError: any; @@ -114,6 +115,7 @@ export class TLSSocket extends net.Socket { this.secureConnecting = true; this._SNICallback = null; this.servername = null; + this.alpnProtocol = null; this.alpnProtocols = tlsOptions.ALPNProtocols; this.authorized = false; this.authorizationError = null; @@ -151,10 +153,21 @@ export class TLSSocket extends net.Socket { handle.afterConnect = async (req: any, status: number) => { try { const conn = await Deno.startTls(handle[kStreamBaseField], options); + try { + const hs = await conn.handshake(); + if (hs.alpnProtocol) { + tlssock.alpnProtocol = hs.alpnProtocol; + } else { + tlssock.alpnProtocol = false; + } + } catch { + // Don't interrupt "secure" event to let the first read/write + // operation emit the error. + } handle[kStreamBaseField] = conn; tlssock.emit("secure"); tlssock.removeListener("end", onConnectEnd); - } catch { + } catch (_) { // TODO(kt3k): Handle this } return afterConnect.call(handle, req, status); @@ -269,6 +282,7 @@ export class ServerImpl extends EventEmitter { // Creates TCP handle and socket directly from Deno.TlsConn. // This works as TLS socket. We don't use TLSSocket class for doing // this because Deno.startTls only supports client side tcp connection. + // TODO(@satyarohith): set TLSSocket.alpnProtocol when we use TLSSocket class. const handle = new TCP(TCPConstants.SOCKET, await listener.accept()); const socket = new net.Socket({ handle }); this.emit("secureConnection", socket); -- cgit v1.2.3 From 27df42f659ae7b77968d31363ade89addb516ea1 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Wed, 23 Oct 2024 21:50:35 -0700 Subject: fix(ext/node): cancel pending ipc writes on channel close (#26504) Fixes the issue described in https://github.com/denoland/deno/issues/23882#issuecomment-2423316362. The parent was starting to send a message right before the process would exit, and the channel closed in the middle of the write. Unlike with reads, we weren't cancelling the pending writes, which resulted in a `Broken pipe` error surfacing to the user. --- ext/node/polyfills/internal/child_process.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/internal/child_process.ts b/ext/node/polyfills/internal/child_process.ts index 65d825fd2..cfff1079f 100644 --- a/ext/node/polyfills/internal/child_process.ts +++ b/ext/node/polyfills/internal/child_process.ts @@ -1339,7 +1339,7 @@ export function setupChannel(target: any, ipc: number) { } } - process.nextTick(handleMessage, msg); + nextTick(handleMessage, msg); } } catch (err) { if ( @@ -1400,7 +1400,7 @@ export function setupChannel(target: any, ipc: number) { if (!target.connected) { const err = new ERR_IPC_CHANNEL_CLOSED(); if (typeof callback === "function") { - process.nextTick(callback, err); + nextTick(callback, err); } else { nextTick(() => target.emit("error", err)); } @@ -1416,7 +1416,18 @@ export function setupChannel(target: any, ipc: number) { .then(() => { control.unrefCounted(); if (callback) { - process.nextTick(callback, null); + nextTick(callback, null); + } + }, (err: Error) => { + control.unrefCounted(); + if (err instanceof Deno.errors.Interrupted) { + // Channel closed on us mid-write. + } else { + if (typeof callback === "function") { + nextTick(callback, err); + } else { + nextTick(() => target.emit("error", err)); + } } }); return queueOk[0]; @@ -1433,7 +1444,7 @@ export function setupChannel(target: any, ipc: number) { target.connected = false; target[kCanDisconnect] = false; control[kControlDisconnect](); - process.nextTick(() => { + nextTick(() => { target.channel = null; core.close(ipc); target.emit("disconnect"); -- cgit v1.2.3 From ef53ce3ac40e2c545397669e61e4e92423555d94 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Thu, 24 Oct 2024 11:46:51 +0200 Subject: fix(node/util): support array formats in `styleText` (#26507) We missed adding support for an array of formats being passed to `util.styleText`. Fixes https://github.com/denoland/deno/issues/26496 --- ext/node/polyfills/internal/util/inspect.mjs | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/internal/util/inspect.mjs b/ext/node/polyfills/internal/util/inspect.mjs index 3a61c387c..ae797449b 100644 --- a/ext/node/polyfills/internal/util/inspect.mjs +++ b/ext/node/polyfills/internal/util/inspect.mjs @@ -565,6 +565,19 @@ export function stripVTControlCharacters(str) { export function styleText(format, text) { validateString(text, "text"); + + if (Array.isArray(format)) { + for (let i = 0; i < format.length; i++) { + const item = format[i]; + const formatCodes = inspect.colors[item]; + if (formatCodes == null) { + validateOneOf(item, "format", Object.keys(inspect.colors)); + } + text = `\u001b[${formatCodes[0]}m${text}\u001b[${formatCodes[1]}m`; + } + return text; + } + const formatCodes = inspect.colors[format]; if (formatCodes == null) { validateOneOf(format, "format", Object.keys(inspect.colors)); -- cgit v1.2.3 From 8dd6177c624649d75ffcacca77e7c4f48cea07a2 Mon Sep 17 00:00:00 2001 From: Nicola Bovolato <61934734+nicolabovolato@users.noreply.github.com> Date: Fri, 25 Oct 2024 00:02:26 +0200 Subject: fix(ext/node): refactor http.ServerResponse into function class (#26210) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While testing, I found out that light-my-request relies on `ServerResponse.connection`, which is deprecated, so I added that and `socket`, the non deprecated property. It also relies on an undocumented `_header` property, apparently for [raw header processing](https://github.com/fastify/light-my-request/blob/v6.1.0/lib/response.js#L180-L186). I added it as an empty string, feel free to provide other approaches. Fixes #19901 Co-authored-by: Bartek Iwańczuk --- ext/node/polyfills/http.ts | 524 ++++++++++++++++++++++++++++----------------- 1 file changed, 324 insertions(+), 200 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/http.ts b/ext/node/polyfills/http.ts index 20bef3009..e7a860ff1 100644 --- a/ext/node/polyfills/http.ts +++ b/ext/node/polyfills/http.ts @@ -34,6 +34,7 @@ import { finished, Readable as NodeReadable, Writable as NodeWritable, + WritableOptions as NodeWritableOptions, } from "node:stream"; import { kUniqueHeaders, @@ -70,6 +71,7 @@ import { resourceForReadableStream } from "ext:deno_web/06_streams.js"; import { UpgradedConn } from "ext:deno_net/01_net.js"; import { STATUS_CODES } from "node:_http_server"; import { methods as METHODS } from "node:_http_common"; +import { deprecate } from "node:util"; const { internalRidSymbol } = core; const { ArrayIsArray, StringPrototypeToLowerCase } = primordials; @@ -1184,49 +1186,95 @@ function onError(self, error, cb) { } } -export class ServerResponse extends NodeWritable { - statusCode = 200; - statusMessage?: string = undefined; - #headers: Record = { __proto__: null }; - #hasNonStringHeaders: boolean = false; - #readable: ReadableStream; - override writable = true; - // used by `npm:on-finished` - finished = false; - headersSent = false; - #resolve: (value: Response | PromiseLike) => void; +export type ServerResponse = { + statusCode: number; + statusMessage?: string; + + _headers: Record; + _hasNonStringHeaders: boolean; + + _readable: ReadableStream; + finished: boolean; + headersSent: boolean; + _resolve: (value: Response | PromiseLike) => void; + // deno-lint-ignore no-explicit-any + _socketOverride: any | null; // deno-lint-ignore no-explicit-any - #socketOverride: any | null = null; + socket: any | null; - static #enqueue(controller: ReadableStreamDefaultController, chunk: Chunk) { - try { - if (typeof chunk === "string") { - controller.enqueue(ENCODER.encode(chunk)); - } else { - controller.enqueue(chunk); - } - } catch (_) { - // The stream might have been closed. Ignore the error. - } - } + setHeader(name: string, value: string | string[]): void; + appendHeader(name: string, value: string | string[]): void; + getHeader(name: string): string | string[]; + removeHeader(name: string): void; + getHeaderNames(): string[]; + getHeaders(): Record; + hasHeader(name: string): boolean; - /** Returns true if the response body should be null with the given - * http status code */ - static #bodyShouldBeNull(status: number) { - return status === 101 || status === 204 || status === 205 || status === 304; - } + writeHead( + status: number, + statusMessage?: string, + headers?: + | Record + | Array<[string, string]>, + ): void; + writeHead( + status: number, + headers?: + | Record + | Array<[string, string]>, + ): void; - constructor( + _ensureHeaders(singleChunk?: Chunk): void; + + respond(final: boolean, singleChunk?: Chunk): void; + // deno-lint-ignore no-explicit-any + end(chunk?: any, encoding?: any, cb?: any): void; + + flushHeaders(): void; + _implicitHeader(): void; + + // Undocumented field used by `npm:light-my-request`. + _header: string; + + assignSocket(socket): void; + detachSocket(socket): void; +} & { -readonly [K in keyof NodeWritable]: NodeWritable[K] }; + +type ServerResponseStatic = { + new ( resolve: (value: Response | PromiseLike) => void, socket: FakeSocket, - ) { - let controller: ReadableByteStreamController; - const readable = new ReadableStream({ - start(c) { - controller = c as ReadableByteStreamController; - }, - }); - super({ + ): ServerResponse; + _enqueue(controller: ReadableStreamDefaultController, chunk: Chunk): void; + _bodyShouldBeNull(statusCode: number): boolean; +}; + +export const ServerResponse = function ( + this: ServerResponse, + resolve: (value: Response | PromiseLike) => void, + socket: FakeSocket, +) { + this.statusCode = 200; + this.statusMessage = undefined; + this._headers = { __proto__: null }; + this._hasNonStringHeaders = false; + this.writable = true; + + // used by `npm:on-finished` + this.finished = false; + this.headersSent = false; + this._socketOverride = null; + + let controller: ReadableByteStreamController; + const readable = new ReadableStream({ + start(c) { + controller = c as ReadableByteStreamController; + }, + }); + + NodeWritable.call( + this, + { autoDestroy: true, defaultEncoding: "utf-8", emitClose: true, @@ -1235,16 +1283,16 @@ export class ServerResponse extends NodeWritable { write: (chunk, encoding, cb) => { // Writes chunks are directly written to the socket if // one is assigned via assignSocket() - if (this.#socketOverride && this.#socketOverride.writable) { - this.#socketOverride.write(chunk, encoding); + if (this._socketOverride && this._socketOverride.writable) { + this._socketOverride.write(chunk, encoding); return cb(); } if (!this.headersSent) { - ServerResponse.#enqueue(controller, chunk); + ServerResponse._enqueue(controller, chunk); this.respond(false); return cb(); } - ServerResponse.#enqueue(controller, chunk); + ServerResponse._enqueue(controller, chunk); return cb(); }, final: (cb) => { @@ -1260,193 +1308,269 @@ export class ServerResponse extends NodeWritable { } return cb(null); }, - }); - this.#readable = readable; - this.#resolve = resolve; - this.socket = socket; + } satisfies NodeWritableOptions, + ); + + this._readable = readable; + this._resolve = resolve; + this.socket = socket; + + this._header = ""; +} as unknown as ServerResponseStatic; + +Object.setPrototypeOf(ServerResponse.prototype, NodeWritable.prototype); +Object.setPrototypeOf(ServerResponse, NodeWritable); + +ServerResponse._enqueue = function ( + this: ServerResponse, + controller: ReadableStreamDefaultController, + chunk: Chunk, +) { + try { + if (typeof chunk === "string") { + controller.enqueue(ENCODER.encode(chunk)); + } else { + controller.enqueue(chunk); + } + } catch (_) { + // The stream might have been closed. Ignore the error. } +}; - setHeader(name: string, value: string | string[]) { - if (Array.isArray(value)) { - this.#hasNonStringHeaders = true; - } - this.#headers[StringPrototypeToLowerCase(name)] = value; - return this; +/** Returns true if the response body should be null with the given + * http status code */ +ServerResponse._bodyShouldBeNull = function ( + this: ServerResponse, + status: number, +) { + return status === 101 || status === 204 || status === 205 || status === 304; +}; + +ServerResponse.prototype.setHeader = function ( + this: ServerResponse, + name: string, + value: string | string[], +) { + if (Array.isArray(value)) { + this._hasNonStringHeaders = true; } + this._headers[StringPrototypeToLowerCase(name)] = value; + return this; +}; - appendHeader(name: string, value: string | string[]) { - const key = StringPrototypeToLowerCase(name); - if (this.#headers[key] === undefined) { - if (Array.isArray(value)) this.#hasNonStringHeaders = true; - this.#headers[key] = value; +ServerResponse.prototype.appendHeader = function ( + this: ServerResponse, + name: string, + value: string | string[], +) { + const key = StringPrototypeToLowerCase(name); + if (this._headers[key] === undefined) { + if (Array.isArray(value)) this._hasNonStringHeaders = true; + this._headers[key] = value; + } else { + this._hasNonStringHeaders = true; + if (!Array.isArray(this._headers[key])) { + this._headers[key] = [this._headers[key]]; + } + const header = this._headers[key]; + if (Array.isArray(value)) { + header.push(...value); } else { - this.#hasNonStringHeaders = true; - if (!Array.isArray(this.#headers[key])) { - this.#headers[key] = [this.#headers[key]]; - } - const header = this.#headers[key]; - if (Array.isArray(value)) { - header.push(...value); - } else { - header.push(value); - } + header.push(value); } - return this; } + return this; +}; - getHeader(name: string) { - return this.#headers[StringPrototypeToLowerCase(name)]; - } - removeHeader(name: string) { - delete this.#headers[StringPrototypeToLowerCase(name)]; - } - getHeaderNames() { - return Object.keys(this.#headers); - } - getHeaders(): Record { - // @ts-ignore Ignore null __proto__ - return { __proto__: null, ...this.#headers }; - } - hasHeader(name: string) { - return Object.hasOwn(this.#headers, name); - } +ServerResponse.prototype.getHeader = function ( + this: ServerResponse, + name: string, +) { + return this._headers[StringPrototypeToLowerCase(name)]; +}; - writeHead( - status: number, - statusMessage?: string, - headers?: - | Record - | Array<[string, string]>, - ): this; - writeHead( - status: number, - headers?: - | Record - | Array<[string, string]>, - ): this; - writeHead( - status: number, - statusMessageOrHeaders?: - | string - | Record - | Array<[string, string]>, - maybeHeaders?: - | Record - | Array<[string, string]>, - ): this { - this.statusCode = status; - - let headers = null; - if (typeof statusMessageOrHeaders === "string") { - this.statusMessage = statusMessageOrHeaders; - if (maybeHeaders !== undefined) { - headers = maybeHeaders; - } - } else if (statusMessageOrHeaders !== undefined) { - headers = statusMessageOrHeaders; - } +ServerResponse.prototype.removeHeader = function ( + this: ServerResponse, + name: string, +) { + delete this._headers[StringPrototypeToLowerCase(name)]; +}; - if (headers !== null) { - if (ArrayIsArray(headers)) { - headers = headers as Array<[string, string]>; - for (let i = 0; i < headers.length; i++) { - this.appendHeader(headers[i][0], headers[i][1]); - } - } else { - headers = headers as Record; - for (const k in headers) { - if (Object.hasOwn(headers, k)) { - this.setHeader(k, headers[k]); - } +ServerResponse.prototype.getHeaderNames = function (this: ServerResponse) { + return Object.keys(this._headers); +}; + +ServerResponse.prototype.getHeaders = function ( + this: ServerResponse, +): Record { + return { __proto__: null, ...this._headers }; +}; + +ServerResponse.prototype.hasHeader = function ( + this: ServerResponse, + name: string, +) { + return Object.hasOwn(this._headers, name); +}; + +ServerResponse.prototype.writeHead = function ( + this: ServerResponse, + status: number, + statusMessageOrHeaders?: + | string + | Record + | Array<[string, string]>, + maybeHeaders?: + | Record + | Array<[string, string]>, +) { + this.statusCode = status; + + let headers = null; + if (typeof statusMessageOrHeaders === "string") { + this.statusMessage = statusMessageOrHeaders; + if (maybeHeaders !== undefined) { + headers = maybeHeaders; + } + } else if (statusMessageOrHeaders !== undefined) { + headers = statusMessageOrHeaders; + } + + if (headers !== null) { + if (ArrayIsArray(headers)) { + headers = headers as Array<[string, string]>; + for (let i = 0; i < headers.length; i++) { + this.appendHeader(headers[i][0], headers[i][1]); + } + } else { + headers = headers as Record; + for (const k in headers) { + if (Object.hasOwn(headers, k)) { + this.setHeader(k, headers[k]); } } } + } - return this; + return this; +}; + +ServerResponse.prototype._ensureHeaders = function ( + this: ServerResponse, + singleChunk?: Chunk, +) { + if (this.statusCode === 200 && this.statusMessage === undefined) { + this.statusMessage = "OK"; + } + if (typeof singleChunk === "string" && !this.hasHeader("content-type")) { + this.setHeader("content-type", "text/plain;charset=UTF-8"); } +}; - #ensureHeaders(singleChunk?: Chunk) { - if (this.statusCode === 200 && this.statusMessage === undefined) { - this.statusMessage = "OK"; - } - if ( - typeof singleChunk === "string" && - !this.hasHeader("content-type") - ) { - this.setHeader("content-type", "text/plain;charset=UTF-8"); - } - } - - respond(final: boolean, singleChunk?: Chunk) { - this.headersSent = true; - this.#ensureHeaders(singleChunk); - let body = singleChunk ?? (final ? null : this.#readable); - if (ServerResponse.#bodyShouldBeNull(this.statusCode)) { - body = null; - } - let headers: Record | [string, string][] = this - .#headers as Record; - if (this.#hasNonStringHeaders) { - headers = []; - // Guard is not needed as this is a null prototype object. - // deno-lint-ignore guard-for-in - for (const key in this.#headers) { - const entry = this.#headers[key]; - if (Array.isArray(entry)) { - for (const value of entry) { - headers.push([key, value]); - } - } else { - headers.push([key, entry]); +ServerResponse.prototype.respond = function ( + this: ServerResponse, + final: boolean, + singleChunk?: Chunk, +) { + this.headersSent = true; + this._ensureHeaders(singleChunk); + let body = singleChunk ?? (final ? null : this._readable); + if (ServerResponse._bodyShouldBeNull(this.statusCode)) { + body = null; + } + let headers: Record | [string, string][] = this + ._headers as Record; + if (this._hasNonStringHeaders) { + headers = []; + // Guard is not needed as this is a null prototype object. + // deno-lint-ignore guard-for-in + for (const key in this._headers) { + const entry = this._headers[key]; + if (Array.isArray(entry)) { + for (const value of entry) { + headers.push([key, value]); } + } else { + headers.push([key, entry]); } } - this.#resolve( - new Response(body, { - headers, - status: this.statusCode, - statusText: this.statusMessage, - }), - ); } + this._resolve( + new Response(body, { + headers, + status: this.statusCode, + statusText: this.statusMessage, + }), + ); +}; +ServerResponse.prototype.end = function ( + this: ServerResponse, // deno-lint-ignore no-explicit-any - override end(chunk?: any, encoding?: any, cb?: any): this { - this.finished = true; - if (!chunk && "transfer-encoding" in this.#headers) { - // FIXME(bnoordhuis) Node sends a zero length chunked body instead, i.e., - // the trailing "0\r\n", but respondWith() just hangs when I try that. - this.#headers["content-length"] = "0"; - delete this.#headers["transfer-encoding"]; - } + chunk?: any, + // deno-lint-ignore no-explicit-any + encoding?: any, + // deno-lint-ignore no-explicit-any + cb?: any, +) { + this.finished = true; + if (!chunk && "transfer-encoding" in this._headers) { + // FIXME(bnoordhuis) Node sends a zero length chunked body instead, i.e., + // the trailing "0\r\n", but respondWith() just hangs when I try that. + this._headers["content-length"] = "0"; + delete this._headers["transfer-encoding"]; + } + + // @ts-expect-error The signature for cb is stricter than the one implemented here + NodeWritable.prototype.end.call(this, chunk, encoding, cb); +}; - // @ts-expect-error The signature for cb is stricter than the one implemented here - return super.end(chunk, encoding, cb); - } +ServerResponse.prototype.flushHeaders = function (this: ServerResponse) { + // no-op +}; - flushHeaders() { - // no-op - } +// Undocumented API used by `npm:compression`. +ServerResponse.prototype._implicitHeader = function (this: ServerResponse) { + this.writeHead(this.statusCode); +}; - // Undocumented API used by `npm:compression`. - _implicitHeader() { - this.writeHead(this.statusCode); +ServerResponse.prototype.assignSocket = function ( + this: ServerResponse, + socket, +) { + if (socket._httpMessage) { + throw new ERR_HTTP_SOCKET_ASSIGNED(); } + socket._httpMessage = this; + this._socketOverride = socket; +}; - assignSocket(socket) { - if (socket._httpMessage) { - throw new ERR_HTTP_SOCKET_ASSIGNED(); - } - socket._httpMessage = this; - this.#socketOverride = socket; - } +ServerResponse.prototype.detachSocket = function ( + this: ServerResponse, + socket, +) { + assert(socket._httpMessage === this); + socket._httpMessage = null; + this._socketOverride = null; +}; - detachSocket(socket) { - assert(socket._httpMessage === this); - socket._httpMessage = null; - this.#socketOverride = null; - } -} +Object.defineProperty(ServerResponse.prototype, "connection", { + get: deprecate( + function (this: ServerResponse) { + return this._socketOverride; + }, + "ServerResponse.prototype.connection is deprecated", + "DEP0066", + ), + set: deprecate( + // deno-lint-ignore no-explicit-any + function (this: ServerResponse, socket: any) { + this._socketOverride = socket; + }, + "ServerResponse.prototype.connection is deprecated", + "DEP0066", + ), +}); // TODO(@AaronO): optimize export class IncomingMessageForServer extends NodeReadable { -- cgit v1.2.3 From 793b155cd31bfc79e4aac4447bd250c9a6d31f6b Mon Sep 17 00:00:00 2001 From: Mayank Kumar <103447831+mayank-Pareek@users.noreply.github.com> Date: Sat, 26 Oct 2024 13:42:14 -0400 Subject: fix(ext/node): use primordials in ext\node\polyfills\internal\crypto\_randomInt.ts (#26534) Towards #24236 --- ext/node/polyfills/internal/crypto/_randomInt.ts | 26 ++++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/internal/crypto/_randomInt.ts b/ext/node/polyfills/internal/crypto/_randomInt.ts index 7f4d703ad..e08b3e963 100644 --- a/ext/node/polyfills/internal/crypto/_randomInt.ts +++ b/ext/node/polyfills/internal/crypto/_randomInt.ts @@ -1,9 +1,15 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -// TODO(petamoriken): enable prefer-primordials for node polyfills -// deno-lint-ignore-file prefer-primordials - import { op_node_random_int } from "ext:core/ops"; +import { primordials } from "ext:core/mod.js"; +const { + Error, + MathCeil, + MathFloor, + MathPow, + NumberIsSafeInteger, + RangeError, +} = primordials; export default function randomInt(max: number): number; export default function randomInt(min: number, max: number): number; @@ -23,7 +29,9 @@ export default function randomInt( cb?: (err: Error | null, n?: number) => void, ): number | void { if (typeof max === "number" && typeof min === "number") { - [max, min] = [min, max]; + const temp = max; + max = min; + min = temp; } if (min === undefined) min = 0; else if (typeof min === "function") { @@ -32,13 +40,13 @@ export default function randomInt( } if ( - !Number.isSafeInteger(min) || - typeof max === "number" && !Number.isSafeInteger(max) + !NumberIsSafeInteger(min) || + typeof max === "number" && !NumberIsSafeInteger(max) ) { throw new Error("max or min is not a Safe Number"); } - if (max - min > Math.pow(2, 48)) { + if (max - min > MathPow(2, 48)) { throw new RangeError("max - min should be less than 2^48!"); } @@ -46,8 +54,8 @@ export default function randomInt( throw new Error("Min is bigger than Max!"); } - min = Math.ceil(min); - max = Math.floor(max); + min = MathCeil(min); + max = MathFloor(max); const result = op_node_random_int(min, max); if (cb) { -- cgit v1.2.3 From c314b2d8577289078d6b00a0dd58f8f36ff6920a Mon Sep 17 00:00:00 2001 From: familyboat <84062528+familyboat@users.noreply.github.com> Date: Sun, 27 Oct 2024 11:04:35 +0800 Subject: fix(ext/node): add path to `fs.stat` and `fs.statSync` error (#26037) --- ext/node/polyfills/_fs/_fs_stat.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/_fs/_fs_stat.ts b/ext/node/polyfills/_fs/_fs_stat.ts index c4ed82d57..d00c81ffb 100644 --- a/ext/node/polyfills/_fs/_fs_stat.ts +++ b/ext/node/polyfills/_fs/_fs_stat.ts @@ -383,7 +383,10 @@ export function stat( Deno.stat(path).then( (stat) => callback(null, CFISBIS(stat, options.bigint)), - (err) => callback(denoErrorToNodeError(err, { syscall: "stat" })), + (err) => + callback( + denoErrorToNodeError(err, { syscall: "stat", path: getPathname(path) }), + ), ); } @@ -417,9 +420,16 @@ export function statSync( return; } if (err instanceof Error) { - throw denoErrorToNodeError(err, { syscall: "stat" }); + throw denoErrorToNodeError(err, { + syscall: "stat", + path: getPathname(path), + }); } else { throw err; } } } + +function getPathname(path: string | URL) { + return typeof path === "string" ? path : path.pathname; +} -- cgit v1.2.3 From 4e38fbd0a3e0ca139314e503494a8d4795007d8a Mon Sep 17 00:00:00 2001 From: snek Date: Mon, 28 Oct 2024 18:16:43 +0100 Subject: fix: report exceptions from nextTick (#26579) Fixes: https://github.com/denoland/deno/issues/24713 Fixes: https://github.com/denoland/deno/issues/25855 --- ext/node/polyfills/_next_tick.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/_next_tick.ts b/ext/node/polyfills/_next_tick.ts index 62470c564..af306a29c 100644 --- a/ext/node/polyfills/_next_tick.ts +++ b/ext/node/polyfills/_next_tick.ts @@ -62,6 +62,8 @@ export function processTicksAndRejections() { callback(...args); } } + } catch (e) { + reportError(e); } finally { // FIXME(bartlomieju): Deno currently doesn't support async hooks // if (destroyHooksExist()) -- cgit v1.2.3 From 46e5ed1a642edcd998d1e22c9b37e0c4aa298e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 29 Oct 2024 00:41:02 +0000 Subject: Revert "fix(ext/node): use primordials in `ext/node/polyfills/https.ts` (#26323)" (#26613) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …s` (#26323)" This reverts commit afb33b3c2597c9ec943f71218b236486fbc86e23. Reverting because it caused a regression - https://github.com/denoland/deno/issues/26612. Closes https://github.com/denoland/deno/issues/26612. --- ext/node/polyfills/https.ts | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/https.ts b/ext/node/polyfills/https.ts index dd24cf048..f60c5e471 100644 --- a/ext/node/polyfills/https.ts +++ b/ext/node/polyfills/https.ts @@ -1,6 +1,9 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright Joyent and Node contributors. All rights reserved. MIT license. +// TODO(petamoriken): enable prefer-primordials for node polyfills +// deno-lint-ignore-file prefer-primordials + import { notImplemented } from "ext:deno_node/_utils.ts"; import { urlToHttpOptions } from "ext:deno_node/internal/url.ts"; import { @@ -14,14 +17,6 @@ import { type ServerHandler, ServerImpl as HttpServer } from "node:http"; import { validateObject } from "ext:deno_node/internal/validators.mjs"; import { kEmptyObject } from "ext:deno_node/internal/util.mjs"; import { Buffer } from "node:buffer"; -import { primordials } from "ext:core/mod.js"; -const { - ArrayPrototypeShift, - ArrayPrototypeUnshift, - ArrayIsArray, - ObjectPrototypeIsPrototypeOf, - ObjectAssign, -} = primordials; export class Server extends HttpServer { constructor(opts, requestListener?: ServerHandler) { @@ -34,11 +29,11 @@ export class Server extends HttpServer { validateObject(opts, "options"); } - if (opts.cert && ArrayIsArray(opts.cert)) { + if (opts.cert && Array.isArray(opts.cert)) { notImplemented("https.Server.opts.cert array type"); } - if (opts.key && ArrayIsArray(opts.key)) { + if (opts.key && Array.isArray(opts.key)) { notImplemented("https.Server.opts.key array type"); } @@ -47,12 +42,10 @@ export class Server extends HttpServer { _additionalServeOptions() { return { - cert: ObjectPrototypeIsPrototypeOf(Buffer, this._opts.cert) - // deno-lint-ignore prefer-primordials + cert: this._opts.cert instanceof Buffer ? this._opts.cert.toString() : this._opts.cert, - key: ObjectPrototypeIsPrototypeOf(Buffer, this._opts.key) - // deno-lint-ignore prefer-primordials + key: this._opts.key instanceof Buffer ? this._opts.key.toString() : this._opts.key, }; @@ -166,18 +159,18 @@ export function request(...args: any[]) { let options = {}; if (typeof args[0] === "string") { - const urlStr = ArrayPrototypeShift(args); + const urlStr = args.shift(); options = urlToHttpOptions(new URL(urlStr)); - } else if (ObjectPrototypeIsPrototypeOf(URL, args[0])) { - options = urlToHttpOptions(ArrayPrototypeShift(args)); + } else if (args[0] instanceof URL) { + options = urlToHttpOptions(args.shift()); } if (args[0] && typeof args[0] !== "function") { - ObjectAssign(options, ArrayPrototypeShift(args)); + Object.assign(options, args.shift()); } options._defaultAgent = globalAgent; - ArrayPrototypeUnshift(args, options); + args.unshift(options); return new HttpsClientRequest(args[0], args[1]); } -- cgit v1.2.3 From a69224ea5bd02f08108aac867c492754095f2d34 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Wed, 30 Oct 2024 02:41:16 +0900 Subject: Revert "fix(ext/node): fix dns.lookup result ordering (#26264)" (#26621) This reverts commit d59599fc187c559ee231882773e1c5a2b932fc3d. Closes #26588 --- ext/node/polyfills/http.ts | 6 +++--- ext/node/polyfills/internal/dns/utils.ts | 14 +++++++++++-- ext/node/polyfills/internal_binding/cares_wrap.ts | 13 +++--------- ext/node/polyfills/net.ts | 24 ++++++++++++++++------- 4 files changed, 35 insertions(+), 22 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/http.ts b/ext/node/polyfills/http.ts index e7a860ff1..e8a189f70 100644 --- a/ext/node/polyfills/http.ts +++ b/ext/node/polyfills/http.ts @@ -51,7 +51,6 @@ import { urlToHttpOptions } from "ext:deno_node/internal/url.ts"; import { kEmptyObject } from "ext:deno_node/internal/util.mjs"; import { constants, TCP } from "ext:deno_node/internal_binding/tcp_wrap.ts"; import { notImplemented, warnNotImplemented } from "ext:deno_node/_utils.ts"; -import { isWindows } from "ext:deno_node/_util/os.ts"; import { connResetException, ERR_HTTP_HEADERS_SENT, @@ -1712,8 +1711,9 @@ export class ServerImpl extends EventEmitter { port = options.port | 0; } - // Use 0.0.0.0 for Windows, and [::] for other platforms. - let hostname = options.host ?? (isWindows ? "0.0.0.0" : "[::]"); + // TODO(bnoordhuis) Node prefers [::] when host is omitted, + // we on the other hand default to 0.0.0.0. + let hostname = options.host ?? "0.0.0.0"; if (hostname == "localhost") { hostname = "127.0.0.1"; } diff --git a/ext/node/polyfills/internal/dns/utils.ts b/ext/node/polyfills/internal/dns/utils.ts index 1e0c3d9ed..226fce93d 100644 --- a/ext/node/polyfills/internal/dns/utils.ts +++ b/ext/node/polyfills/internal/dns/utils.ts @@ -416,10 +416,20 @@ export function emitInvalidHostnameWarning(hostname: string) { ); } -let dnsOrder = getOptionValue("--dns-result-order") || "verbatim"; +let dnsOrder = getOptionValue("--dns-result-order") || "ipv4first"; export function getDefaultVerbatim() { - return dnsOrder !== "ipv4first"; + switch (dnsOrder) { + case "verbatim": { + return true; + } + case "ipv4first": { + return false; + } + default: { + return false; + } + } } /** diff --git a/ext/node/polyfills/internal_binding/cares_wrap.ts b/ext/node/polyfills/internal_binding/cares_wrap.ts index cbfea40b2..6feb7faf0 100644 --- a/ext/node/polyfills/internal_binding/cares_wrap.ts +++ b/ext/node/polyfills/internal_binding/cares_wrap.ts @@ -75,18 +75,11 @@ export function getaddrinfo( const recordTypes: ("A" | "AAAA")[] = []; - if (family === 6) { - recordTypes.push("AAAA"); - } else if (family === 4) { + if (family === 0 || family === 4) { recordTypes.push("A"); - } else if (family === 0 && hostname === "localhost") { - // Ipv6 is preferred over Ipv4 for localhost + } + if (family === 0 || family === 6) { recordTypes.push("AAAA"); - recordTypes.push("A"); - } else if (family === 0) { - // Only get Ipv4 addresses for the other hostnames - // This simulates what `getaddrinfo` does when the family is not specified - recordTypes.push("A"); } (async () => { diff --git a/ext/node/polyfills/net.ts b/ext/node/polyfills/net.ts index 35d273be9..48e1d0de8 100644 --- a/ext/node/polyfills/net.ts +++ b/ext/node/polyfills/net.ts @@ -1871,13 +1871,23 @@ function _setupListenHandle( // Try to bind to the unspecified IPv6 address, see if IPv6 is available if (!address && typeof fd !== "number") { - if (isWindows) { - address = DEFAULT_IPV4_ADDR; - addressType = 4; - } else { - address = DEFAULT_IPV6_ADDR; - addressType = 6; - } + // TODO(@bartlomieju): differs from Node which tries to bind to IPv6 first + // when no address is provided. + // + // Forcing IPv4 as a workaround for Deno not aligning with Node on + // implicit binding on Windows. + // + // REF: https://github.com/denoland/deno/issues/10762 + // rval = _createServerHandle(DEFAULT_IPV6_ADDR, port, 6, fd, flags); + + // if (typeof rval === "number") { + // rval = null; + address = DEFAULT_IPV4_ADDR; + addressType = 4; + // } else { + // address = DEFAULT_IPV6_ADDR; + // addressType = 6; + // } } if (rval === null) { -- cgit v1.2.3 From a346071dd2bd822a7c2abcb187406efe7d1d1471 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:13:32 -0700 Subject: fix(ext/node): return `this` from `http.Server.ref/unref()` (#26647) Fixes https://github.com/denoland/deno/issues/26642 --- ext/node/polyfills/http.ts | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/http.ts b/ext/node/polyfills/http.ts index e8a189f70..9a920adee 100644 --- a/ext/node/polyfills/http.ts +++ b/ext/node/polyfills/http.ts @@ -1802,6 +1802,8 @@ export class ServerImpl extends EventEmitter { this.#server.ref(); } this.#unref = false; + + return this; } unref() { @@ -1809,6 +1811,8 @@ export class ServerImpl extends EventEmitter { this.#server.unref(); } this.#unref = true; + + return this; } close(cb?: (err?: Error) => void): this { -- cgit v1.2.3 From 951103fc8de952f32386121d3f0e4803fe267ea4 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:02:17 -0700 Subject: fix(ext/node): convert errors from `fs.readFile/fs.readFileSync` to node format (#26632) Fixes the original issue reported in #26404. storybook runs into other errors after this PR (the new errors will be fixed in other PRs). Some code used by a dependency of storybook does a [string comparison on the error message](https://github.com/chromaui/chromatic-cli/blob/ce30b2be343cb96a0826390b95ea42befb2be547/node-src/lib/getConfiguration.ts#L88-L92) thrown here to check for a file not found error. --- ext/node/polyfills/_fs/_fs_readFile.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/_fs/_fs_readFile.ts b/ext/node/polyfills/_fs/_fs_readFile.ts index 0f05ee167..cf7e0305d 100644 --- a/ext/node/polyfills/_fs/_fs_readFile.ts +++ b/ext/node/polyfills/_fs/_fs_readFile.ts @@ -19,6 +19,7 @@ import { TextEncodings, } from "ext:deno_node/_utils.ts"; import { FsFile } from "ext:deno_fs/30_fs.js"; +import { denoErrorToNodeError } from "ext:deno_node/internal/errors.ts"; function maybeDecode(data: Uint8Array, encoding: TextEncodings): string; function maybeDecode( @@ -87,7 +88,7 @@ export function readFile( } const buffer = maybeDecode(data, encoding); (cb as BinaryCallback)(null, buffer); - }, (err) => cb && cb(err)); + }, (err) => cb && cb(denoErrorToNodeError(err))); } } @@ -117,7 +118,12 @@ export function readFileSync( opt?: FileOptionsArgument, ): string | Buffer { path = path instanceof URL ? pathFromURL(path) : path; - const data = Deno.readFileSync(path); + let data; + try { + data = Deno.readFileSync(path); + } catch (err) { + throw denoErrorToNodeError(err); + } const encoding = getEncoding(opt); if (encoding && encoding !== "binary") { const text = maybeDecode(data, encoding); -- cgit v1.2.3 From 6d44952d4daaaab8ed9ec82212d2256c058eb05d Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:02:31 -0700 Subject: fix(ext/node): resolve exports even if parent module filename isn't present (#26553) Fixes https://github.com/denoland/deno/issues/26505 I'm not exactly sure how this case comes about (I tried to write tests for it but couldn't manage to reproduce it), but what happens is the parent filename ends up null, and we bail out of resolving the specifier in package exports. I've checked, and in node the parent filename is also null (so that's not a bug on our part), but node continues to resolve even in that case. So this PR should match node's behavior more closely than we currently do. --- ext/node/polyfills/01_require.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/01_require.js b/ext/node/polyfills/01_require.js index 5b0980c31..7935903a8 100644 --- a/ext/node/polyfills/01_require.js +++ b/ext/node/polyfills/01_require.js @@ -523,17 +523,13 @@ function resolveExports( return; } - if (!parentPath) { - return false; - } - return op_require_resolve_exports( usesLocalNodeModulesDir, modulesPath, request, name, expansion, - parentPath, + parentPath ?? "", ) ?? false; } -- cgit v1.2.3 From 6c6bbeb97495e8c3e8eac7bea27bf836f02b575f Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Thu, 31 Oct 2024 22:18:33 -0700 Subject: fix(node): Implement `os.userInfo` properly, add missing `toPrimitive` (#24702) Fixes the implementation of `os.userInfo`, and adds a missing `toPrimitive` for `tmpdir`. This allows us to enable the corresponding node_compat test. --- ext/node/polyfills/internal/errors.ts | 13 --------- ext/node/polyfills/os.ts | 53 +++++++++++++++-------------------- 2 files changed, 23 insertions(+), 43 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/internal/errors.ts b/ext/node/polyfills/internal/errors.ts index 51bd7a025..5a3d4437a 100644 --- a/ext/node/polyfills/internal/errors.ts +++ b/ext/node/polyfills/internal/errors.ts @@ -2558,19 +2558,6 @@ export class ERR_FS_RMDIR_ENOTDIR extends NodeSystemError { } } -export class ERR_OS_NO_HOMEDIR extends NodeSystemError { - constructor() { - const code = isWindows ? "ENOENT" : "ENOTDIR"; - const ctx: NodeSystemErrorCtx = { - message: "not a directory", - syscall: "home", - code, - errno: isWindows ? osConstants.errno.ENOENT : osConstants.errno.ENOTDIR, - }; - super(code, ctx, "Path is not a directory"); - } -} - export class ERR_HTTP_SOCKET_ASSIGNED extends NodeError { constructor() { super( diff --git a/ext/node/polyfills/os.ts b/ext/node/polyfills/os.ts index e47e8679e..edc89ed2c 100644 --- a/ext/node/polyfills/os.ts +++ b/ext/node/polyfills/os.ts @@ -28,16 +28,17 @@ import { op_homedir, op_node_os_get_priority, op_node_os_set_priority, - op_node_os_username, + op_node_os_user_info, } from "ext:core/ops"; import { validateIntegerRange } from "ext:deno_node/_utils.ts"; import process from "node:process"; import { isWindows } from "ext:deno_node/_util/os.ts"; -import { ERR_OS_NO_HOMEDIR } from "ext:deno_node/internal/errors.ts"; import { os } from "ext:deno_node/internal_binding/constants.ts"; import { osUptime } from "ext:runtime/30_os.js"; import { Buffer } from "ext:deno_node/internal/buffer.mjs"; +import { primordials } from "ext:core/mod.js"; +const { StringPrototypeEndsWith, StringPrototypeSlice } = primordials; export const constants = os; @@ -136,6 +137,8 @@ export function arch(): string { (uptime as any)[Symbol.toPrimitive] = (): number => uptime(); // deno-lint-ignore no-explicit-any (machine as any)[Symbol.toPrimitive] = (): string => machine(); +// deno-lint-ignore no-explicit-any +(tmpdir as any)[Symbol.toPrimitive] = (): string | null => tmpdir(); export function cpus(): CPUCoreInfo[] { return op_cpus(); @@ -268,26 +271,27 @@ export function setPriority(pid: number, priority?: number) { export function tmpdir(): string | null { /* This follows the node js implementation, but has a few differences: - * On windows, if none of the environment variables are defined, - we return null. - * On unix we use a plain Deno.env.get, instead of safeGetenv, + * We use a plain Deno.env.get, instead of safeGetenv, which special cases setuid binaries. - * Node removes a single trailing / or \, we remove all. */ if (isWindows) { - const temp = Deno.env.get("TEMP") || Deno.env.get("TMP"); - if (temp) { - return temp.replace(/(? 1 && StringPrototypeEndsWith(temp, "\\") && + !StringPrototypeEndsWith(temp, ":\\") + ) { + temp = StringPrototypeSlice(temp, 0, -1); } - return null; + + return temp; } else { // !isWindows - const temp = Deno.env.get("TMPDIR") || Deno.env.get("TMP") || + let temp = Deno.env.get("TMPDIR") || Deno.env.get("TMP") || Deno.env.get("TEMP") || "/tmp"; - return temp.replace(/(? 1 && StringPrototypeEndsWith(temp, "/")) { + temp = StringPrototypeSlice(temp, 0, -1); + } + return temp; } } @@ -320,7 +324,6 @@ export function uptime(): number { return osUptime(); } -/** Not yet implemented */ export function userInfo( options: UserInfoOptions = { encoding: "utf-8" }, ): UserInfo { @@ -331,20 +334,10 @@ export function userInfo( uid = -1; gid = -1; } - - // TODO(@crowlKats): figure out how to do this correctly: - // The value of homedir returned by os.userInfo() is provided by the operating system. - // This differs from the result of os.homedir(), which queries environment - // variables for the home directory before falling back to the operating system response. - let _homedir = homedir(); - if (!_homedir) { - throw new ERR_OS_NO_HOMEDIR(); - } - let shell = isWindows ? null : (Deno.env.get("SHELL") || null); - let username = op_node_os_username(); + let { username, homedir, shell } = op_node_os_user_info(uid); if (options?.encoding === "buffer") { - _homedir = _homedir ? Buffer.from(_homedir) : _homedir; + homedir = homedir ? Buffer.from(homedir) : homedir; shell = shell ? Buffer.from(shell) : shell; username = Buffer.from(username); } @@ -352,7 +345,7 @@ export function userInfo( return { uid, gid, - homedir: _homedir, + homedir, shell, username, }; -- cgit v1.2.3 From 826e42a5b5880c974ae019a7a21aade6a718062c Mon Sep 17 00:00:00 2001 From: David Sherret Date: Fri, 1 Nov 2024 12:27:00 -0400 Subject: fix: improved support for cjs and cts modules (#26558) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * cts support * better cjs/cts type checking * deno compile cjs/cts support * More efficient detect cjs (going towards stabilization) * Determination of whether .js, .ts, .jsx, or .tsx is cjs or esm is only done after loading * Support `import x = require(...);` Co-authored-by: Bartek Iwańczuk --- ext/node/polyfills/01_require.js | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/01_require.js b/ext/node/polyfills/01_require.js index 7935903a8..296d819aa 100644 --- a/ext/node/polyfills/01_require.js +++ b/ext/node/polyfills/01_require.js @@ -1071,13 +1071,35 @@ Module._extensions[".js"] = function (module, filename) { } else if (pkg?.type === "commonjs") { format = "commonjs"; } - } else if (StringPrototypeEndsWith(filename, ".cjs")) { - format = "commonjs"; } module._compile(content, filename, format); }; +Module._extensions[".ts"] = + Module._extensions[".jsx"] = + Module._extensions[".tsx"] = + function (module, filename) { + const content = op_require_read_file(filename); + + let format; + const pkg = op_require_read_closest_package_json(filename); + if (pkg?.type === "module") { + format = "module"; + } else if (pkg?.type === "commonjs") { + format = "commonjs"; + } + + module._compile(content, filename, format); + }; + +Module._extensions[".cjs"] = + Module._extensions[".cts"] = + function (module, filename) { + const content = op_require_read_file(filename); + module._compile(content, filename, "commonjs"); + }; + function loadESMFromCJS(module, filename, code) { const namespace = op_import_sync( url.pathToFileURL(filename).toString(), @@ -1087,7 +1109,10 @@ function loadESMFromCJS(module, filename, code) { module.exports = namespace; } -Module._extensions[".mjs"] = function (module, filename) { +Module._extensions[".mjs"] = Module._extensions[".mts"] = function ( + module, + filename, +) { loadESMFromCJS(module, filename); }; -- cgit v1.2.3 From fb1d33a7111e45e9b414cfe922a5db5ee4daf3ea Mon Sep 17 00:00:00 2001 From: Kenta Moriuchi Date: Tue, 5 Nov 2024 02:17:11 +0900 Subject: chore: update dlint to v0.68.0 for internal (#26711) --- ext/node/polyfills/_fs/_fs_common.ts | 1 + ext/node/polyfills/_fs/_fs_open.ts | 4 ++-- ext/node/polyfills/_fs/_fs_readv.ts | 1 + ext/node/polyfills/internal/crypto/keygen.ts | 1 + ext/node/polyfills/internal/crypto/random.ts | 1 + ext/node/polyfills/internal_binding/http_parser.ts | 1 + ext/node/polyfills/vm.js | 1 + 7 files changed, 8 insertions(+), 2 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/_fs/_fs_common.ts b/ext/node/polyfills/_fs/_fs_common.ts index ac0bf5a55..a29548bb3 100644 --- a/ext/node/polyfills/_fs/_fs_common.ts +++ b/ext/node/polyfills/_fs/_fs_common.ts @@ -20,6 +20,7 @@ import { notImplemented, TextEncodings, } from "ext:deno_node/_utils.ts"; +import { type Buffer } from "node:buffer"; export type CallbackWithError = (err: ErrnoException | null) => void; diff --git a/ext/node/polyfills/_fs/_fs_open.ts b/ext/node/polyfills/_fs/_fs_open.ts index 8bd989790..31ca4bb61 100644 --- a/ext/node/polyfills/_fs/_fs_open.ts +++ b/ext/node/polyfills/_fs/_fs_open.ts @@ -147,8 +147,8 @@ export function open( export function openPromise( path: string | Buffer | URL, - flags?: openFlags = "r", - mode? = 0o666, + flags: openFlags = "r", + mode = 0o666, ): Promise { return new Promise((resolve, reject) => { open(path, flags, mode, (err, fd) => { diff --git a/ext/node/polyfills/_fs/_fs_readv.ts b/ext/node/polyfills/_fs/_fs_readv.ts index 384f5e319..2259f029a 100644 --- a/ext/node/polyfills/_fs/_fs_readv.ts +++ b/ext/node/polyfills/_fs/_fs_readv.ts @@ -15,6 +15,7 @@ import { maybeCallback } from "ext:deno_node/_fs/_fs_common.ts"; import { validateInteger } from "ext:deno_node/internal/validators.mjs"; import * as io from "ext:deno_io/12_io.js"; import { op_fs_seek_async, op_fs_seek_sync } from "ext:core/ops"; +import process from "node:process"; type Callback = ( err: ErrnoException | null, diff --git a/ext/node/polyfills/internal/crypto/keygen.ts b/ext/node/polyfills/internal/crypto/keygen.ts index a40c76c0d..44bfd8327 100644 --- a/ext/node/polyfills/internal/crypto/keygen.ts +++ b/ext/node/polyfills/internal/crypto/keygen.ts @@ -29,6 +29,7 @@ import { } from "ext:deno_node/internal/validators.mjs"; import { Buffer } from "node:buffer"; import { KeyFormat, KeyType } from "ext:deno_node/internal/crypto/types.ts"; +import process from "node:process"; import { op_node_generate_dh_group_key, diff --git a/ext/node/polyfills/internal/crypto/random.ts b/ext/node/polyfills/internal/crypto/random.ts index 4219414dc..a41b86819 100644 --- a/ext/node/polyfills/internal/crypto/random.ts +++ b/ext/node/polyfills/internal/crypto/random.ts @@ -38,6 +38,7 @@ import { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE, } from "ext:deno_node/internal/errors.ts"; +import { Buffer } from "node:buffer"; export { default as randomBytes } from "ext:deno_node/internal/crypto/_randomBytes.ts"; export { diff --git a/ext/node/polyfills/internal_binding/http_parser.ts b/ext/node/polyfills/internal_binding/http_parser.ts index ca4f896e2..bad10d985 100644 --- a/ext/node/polyfills/internal_binding/http_parser.ts +++ b/ext/node/polyfills/internal_binding/http_parser.ts @@ -126,6 +126,7 @@ ObjectSetPrototypeOf(HTTPParser.prototype, AsyncWrap.prototype); function defineProps(obj: object, props: Record) { for (const entry of new SafeArrayIterator(ObjectEntries(props))) { ObjectDefineProperty(obj, entry[0], { + __proto__: null, value: entry[1], enumerable: true, writable: true, diff --git a/ext/node/polyfills/vm.js b/ext/node/polyfills/vm.js index 183ddad2f..b64c847c5 100644 --- a/ext/node/polyfills/vm.js +++ b/ext/node/polyfills/vm.js @@ -182,6 +182,7 @@ function getContextOptions(options) { let defaultContextNameIndex = 1; export function createContext( + // deno-lint-ignore prefer-primordials contextObject = {}, options = { __proto__: null }, ) { -- cgit v1.2.3 From 84aee0be9aac53499a757973a8a2054d2a44e479 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:08:29 -0800 Subject: fix(ext/node): add `findSourceMap` to the default export of `node:module` (#26720) Next.js 15.0.2 tries to use this and errors out --- ext/node/polyfills/01_require.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/01_require.js b/ext/node/polyfills/01_require.js index 296d819aa..d818bb572 100644 --- a/ext/node/polyfills/01_require.js +++ b/ext/node/polyfills/01_require.js @@ -1312,6 +1312,8 @@ export function findSourceMap(_path) { return undefined; } +Module.findSourceMap = findSourceMap; + /** * @param {string | URL} _specifier * @param {string | URL} _parentUrl -- cgit v1.2.3 From 700f54a13cce0fcdcf19d1893e3254579c7347f4 Mon Sep 17 00:00:00 2001 From: snek Date: Wed, 6 Nov 2024 15:08:26 +0100 Subject: fix(ext/node): better inspector support (#26471) implement local inspector future changes: - wire up InspectorServer to enable open/close/url - wire up connectToMainThread Fixes https://github.com/denoland/deno/issues/25004 --- ext/node/polyfills/inspector.js | 210 +++++++++++++++++++++++++++++++ ext/node/polyfills/inspector.ts | 82 ------------ ext/node/polyfills/inspector/promises.js | 20 +++ 3 files changed, 230 insertions(+), 82 deletions(-) create mode 100644 ext/node/polyfills/inspector.js delete mode 100644 ext/node/polyfills/inspector.ts create mode 100644 ext/node/polyfills/inspector/promises.js (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/inspector.js b/ext/node/polyfills/inspector.js new file mode 100644 index 000000000..7eb15ce91 --- /dev/null +++ b/ext/node/polyfills/inspector.js @@ -0,0 +1,210 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import process from "node:process"; +import { EventEmitter } from "node:events"; +import { primordials } from "ext:core/mod.js"; +import { + op_get_extras_binding_object, + op_inspector_close, + op_inspector_connect, + op_inspector_disconnect, + op_inspector_dispatch, + op_inspector_emit_protocol_event, + op_inspector_enabled, + op_inspector_open, + op_inspector_url, + op_inspector_wait, +} from "ext:core/ops"; +import { + isUint32, + validateFunction, + validateInt32, + validateObject, + validateString, +} from "ext:deno_node/internal/validators.mjs"; +import { + ERR_INSPECTOR_ALREADY_ACTIVATED, + ERR_INSPECTOR_ALREADY_CONNECTED, + ERR_INSPECTOR_CLOSED, + ERR_INSPECTOR_COMMAND, + ERR_INSPECTOR_NOT_ACTIVE, + ERR_INSPECTOR_NOT_CONNECTED, + ERR_INSPECTOR_NOT_WORKER, +} from "ext:deno_node/internal/errors.ts"; + +const { + SymbolDispose, + JSONParse, + JSONStringify, + SafeMap, +} = primordials; + +class Session extends EventEmitter { + #connection = null; + #nextId = 1; + #messageCallbacks = new SafeMap(); + + connect() { + if (this.#connection) { + throw new ERR_INSPECTOR_ALREADY_CONNECTED("The inspector session"); + } + this.#connection = op_inspector_connect(false, (m) => this.#onMessage(m)); + } + + connectToMainThread() { + if (isMainThread) { + throw new ERR_INSPECTOR_NOT_WORKER(); + } + if (this.#connection) { + throw new ERR_INSPECTOR_ALREADY_CONNECTED("The inspector session"); + } + this.#connection = op_inspector_connect(true, (m) => this.#onMessage(m)); + } + + #onMessage(message) { + const parsed = JSONParse(message); + try { + if (parsed.id) { + const callback = this.#messageCallbacks.get(parsed.id); + this.#messageCallbacks.delete(parsed.id); + if (callback) { + if (parsed.error) { + return callback( + new ERR_INSPECTOR_COMMAND( + parsed.error.code, + parsed.error.message, + ), + ); + } + + callback(null, parsed.result); + } + } else { + this.emit(parsed.method, parsed); + this.emit("inspectorNotification", parsed); + } + } catch (error) { + process.emitWarning(error); + } + } + + post(method, params, callback) { + validateString(method, "method"); + if (!callback && typeof params === "function") { + callback = params; + params = null; + } + if (params) { + validateObject(params, "params"); + } + if (callback) { + validateFunction(callback, "callback"); + } + + if (!this.#connection) { + throw new ERR_INSPECTOR_NOT_CONNECTED(); + } + const id = this.#nextId++; + const message = { id, method }; + if (params) { + message.params = params; + } + if (callback) { + this.#messageCallbacks.set(id, callback); + } + op_inspector_dispatch(this.#connection, JSONStringify(message)); + } + + disconnect() { + if (!this.#connection) { + return; + } + op_inspector_disconnect(this.#connection); + this.#connection = null; + // deno-lint-ignore prefer-primordials + for (const callback of this.#messageCallbacks.values()) { + process.nextTick(callback, new ERR_INSPECTOR_CLOSED()); + } + this.#messageCallbacks.clear(); + this.#nextId = 1; + } +} + +function open(port, host, wait) { + if (op_inspector_enabled()) { + throw new ERR_INSPECTOR_ALREADY_ACTIVATED(); + } + // inspectorOpen() currently does not typecheck its arguments and adding + // such checks would be a potentially breaking change. However, the native + // open() function requires the port to fit into a 16-bit unsigned integer, + // causing an integer overflow otherwise, so we at least need to prevent that. + if (isUint32(port)) { + validateInt32(port, "port", 0, 65535); + } else { + // equiv of handling args[0]->IsUint32() + port = undefined; + } + if (typeof host !== "string") { + // equiv of handling args[1]->IsString() + host = undefined; + } + op_inspector_open(port, host); + if (wait) { + op_inspector_wait(); + } + + return { + __proto__: null, + [SymbolDispose]() { + _debugEnd(); + }, + }; +} + +function close() { + op_inspector_close(); +} + +function url() { + return op_inspector_url(); +} + +function waitForDebugger() { + if (!op_inspector_wait()) { + throw new ERR_INSPECTOR_NOT_ACTIVE(); + } +} + +function broadcastToFrontend(eventName, params) { + validateString(eventName, "eventName"); + if (params) { + validateObject(params, "params"); + } + op_inspector_emit_protocol_event(eventName, JSONStringify(params ?? {})); +} + +const Network = { + requestWillBeSent: (params) => + broadcastToFrontend("Network.requestWillBeSent", params), + responseReceived: (params) => + broadcastToFrontend("Network.responseReceived", params), + loadingFinished: (params) => + broadcastToFrontend("Network.loadingFinished", params), + loadingFailed: (params) => + broadcastToFrontend("Network.loadingFailed", params), +}; + +const console = op_get_extras_binding_object().console; + +export { close, console, Network, open, Session, url, waitForDebugger }; + +export default { + open, + close, + url, + waitForDebugger, + console, + Session, + Network, +}; diff --git a/ext/node/polyfills/inspector.ts b/ext/node/polyfills/inspector.ts deleted file mode 100644 index 9de86ab14..000000000 --- a/ext/node/polyfills/inspector.ts +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -// Copyright Joyent and Node contributors. All rights reserved. MIT license. - -import { EventEmitter } from "node:events"; -import { notImplemented } from "ext:deno_node/_utils.ts"; -import { primordials } from "ext:core/mod.js"; - -const { - SafeMap, -} = primordials; - -class Session extends EventEmitter { - #connection = null; - #nextId = 1; - #messageCallbacks = new SafeMap(); - - /** Connects the session to the inspector back-end. */ - connect() { - notImplemented("inspector.Session.prototype.connect"); - } - - /** Connects the session to the main thread - * inspector back-end. */ - connectToMainThread() { - notImplemented("inspector.Session.prototype.connectToMainThread"); - } - - /** Posts a message to the inspector back-end. */ - post( - _method: string, - _params?: Record, - _callback?: (...args: unknown[]) => void, - ) { - notImplemented("inspector.Session.prototype.post"); - } - - /** Immediately closes the session, all pending - * message callbacks will be called with an - * error. - */ - disconnect() { - notImplemented("inspector.Session.prototype.disconnect"); - } -} - -/** Activates inspector on host and port. - * See https://nodejs.org/api/inspector.html#inspectoropenport-host-wait */ -function open(_port?: number, _host?: string, _wait?: boolean) { - notImplemented("inspector.Session.prototype.open"); -} - -/** Deactivate the inspector. Blocks until there are no active connections. - * See https://nodejs.org/api/inspector.html#inspectorclose */ -function close() { - notImplemented("inspector.Session.prototype.close"); -} - -/** Return the URL of the active inspector, or undefined if there is none. - * See https://nodejs.org/api/inspector.html#inspectorurl */ -function url() { - // TODO(kt3k): returns undefined for now, which means the inspector is not activated. - return undefined; -} - -/** Blocks until a client (existing or connected later) has sent Runtime.runIfWaitingForDebugger command. - * See https://nodejs.org/api/inspector.html#inspectorwaitfordebugger */ -function waitForDebugger() { - notImplemented("inspector.wairForDebugger"); -} - -const console = globalThis.console; - -export { close, console, open, Session, url, waitForDebugger }; - -export default { - close, - console, - open, - Session, - url, - waitForDebugger, -}; diff --git a/ext/node/polyfills/inspector/promises.js b/ext/node/polyfills/inspector/promises.js new file mode 100644 index 000000000..3483e53f5 --- /dev/null +++ b/ext/node/polyfills/inspector/promises.js @@ -0,0 +1,20 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import inspector from "node:inspector"; +import { promisify } from "ext:deno_node/internal/util.mjs"; + +class Session extends inspector.Session { + constructor() { + super(); + } +} +Session.prototype.post = promisify(inspector.Session.prototype.post); + +export * from "node:inspector"; +export { Session }; + +export default { + ...inspector, + Session, +}; -- cgit v1.2.3 From b3a3d84ce249ff126f92e7a0849ec0a6ce26e973 Mon Sep 17 00:00:00 2001 From: Satya Rohith Date: Wed, 6 Nov 2024 19:42:24 +0530 Subject: fix(node:zlib): gzip & gzipSync should accept ArrayBuffer (#26762) Closes https://github.com/denoland/deno/issues/26638 --- ext/node/polyfills/_zlib.mjs | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/_zlib.mjs b/ext/node/polyfills/_zlib.mjs index 851bd602f..07fc440ef 100644 --- a/ext/node/polyfills/_zlib.mjs +++ b/ext/node/polyfills/_zlib.mjs @@ -14,6 +14,7 @@ import { nextTick } from "ext:deno_node/_next_tick.ts"; import { isAnyArrayBuffer, isArrayBufferView, + isUint8Array, } from "ext:deno_node/internal/util/types.ts"; var kRangeErrorMessage = "Cannot create final Buffer. It would be larger " + @@ -158,6 +159,12 @@ export const inflateRawSync = function (buffer, opts) { function sanitizeInput(input) { if (typeof input === "string") input = Buffer.from(input); + if (isArrayBufferView(input) && !isUint8Array(input)) { + input = Buffer.from(input.buffer, input.byteOffset, input.byteLength); + } else if (isAnyArrayBuffer(input)) { + input = Buffer.from(input); + } + if ( !Buffer.isBuffer(input) && (input.buffer && !input.buffer.constructor === ArrayBuffer) -- cgit v1.2.3 From c3c2b379669b17e5fdcbe5e62662404ca22c71c6 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Tue, 12 Nov 2024 19:54:47 +0900 Subject: fix(ext/node): add autoSelectFamily option to net.createConnection (#26661) --- ext/node/polyfills/internal/errors.ts | 26 +- ext/node/polyfills/internal/net.ts | 1 + ext/node/polyfills/internal_binding/uv.ts | 2 + ext/node/polyfills/net.ts | 490 +++++++++++++++++++++++++++++- 4 files changed, 504 insertions(+), 15 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/internal/errors.ts b/ext/node/polyfills/internal/errors.ts index 5a3d4437a..962ca86e9 100644 --- a/ext/node/polyfills/internal/errors.ts +++ b/ext/node/polyfills/internal/errors.ts @@ -18,7 +18,7 @@ */ import { primordials } from "ext:core/mod.js"; -const { JSONStringify, SymbolFor } = primordials; +const { JSONStringify, SafeArrayIterator, SymbolFor } = primordials; import { format, inspect } from "ext:deno_node/internal/util/inspect.mjs"; import { codes } from "ext:deno_node/internal/error_codes.ts"; import { @@ -1874,6 +1874,11 @@ export class ERR_SOCKET_CLOSED extends NodeError { super("ERR_SOCKET_CLOSED", `Socket is closed`); } } +export class ERR_SOCKET_CONNECTION_TIMEOUT extends NodeError { + constructor() { + super("ERR_SOCKET_CONNECTION_TIMEOUT", `Socket connection timeout`); + } +} export class ERR_SOCKET_DGRAM_IS_CONNECTED extends NodeError { constructor() { super("ERR_SOCKET_DGRAM_IS_CONNECTED", `Already connected`); @@ -2633,11 +2638,30 @@ export function aggregateTwoErrors( } return innerError || outerError; } + +export class NodeAggregateError extends AggregateError { + code: string; + constructor(errors, message) { + super(new SafeArrayIterator(errors), message); + this.code = errors[0]?.code; + } + + get [kIsNodeError]() { + return true; + } + + // deno-lint-ignore adjacent-overload-signatures + get ["constructor"]() { + return AggregateError; + } +} + codes.ERR_IPC_CHANNEL_CLOSED = ERR_IPC_CHANNEL_CLOSED; codes.ERR_INVALID_ARG_TYPE = ERR_INVALID_ARG_TYPE; codes.ERR_INVALID_ARG_VALUE = ERR_INVALID_ARG_VALUE; codes.ERR_OUT_OF_RANGE = ERR_OUT_OF_RANGE; codes.ERR_SOCKET_BAD_PORT = ERR_SOCKET_BAD_PORT; +codes.ERR_SOCKET_CONNECTION_TIMEOUT = ERR_SOCKET_CONNECTION_TIMEOUT; codes.ERR_BUFFER_OUT_OF_BOUNDS = ERR_BUFFER_OUT_OF_BOUNDS; codes.ERR_UNKNOWN_ENCODING = ERR_UNKNOWN_ENCODING; codes.ERR_PARSE_ARGS_INVALID_OPTION_VALUE = ERR_PARSE_ARGS_INVALID_OPTION_VALUE; diff --git a/ext/node/polyfills/internal/net.ts b/ext/node/polyfills/internal/net.ts index 144612626..a3dcb3ed2 100644 --- a/ext/node/polyfills/internal/net.ts +++ b/ext/node/polyfills/internal/net.ts @@ -95,4 +95,5 @@ export function makeSyncWrite(fd: number) { }; } +export const kReinitializeHandle = Symbol("kReinitializeHandle"); export const normalizedArgsSymbol = Symbol("normalizedArgs"); diff --git a/ext/node/polyfills/internal_binding/uv.ts b/ext/node/polyfills/internal_binding/uv.ts index aa468a0a5..6cd70a7e8 100644 --- a/ext/node/polyfills/internal_binding/uv.ts +++ b/ext/node/polyfills/internal_binding/uv.ts @@ -530,10 +530,12 @@ export function mapSysErrnoToUvErrno(sysErrno: number): number { export const UV_EAI_MEMORY = codeMap.get("EAI_MEMORY")!; export const UV_EBADF = codeMap.get("EBADF")!; +export const UV_ECANCELED = codeMap.get("ECANCELED")!; export const UV_EEXIST = codeMap.get("EEXIST"); export const UV_EINVAL = codeMap.get("EINVAL")!; export const UV_ENOENT = codeMap.get("ENOENT"); export const UV_ENOTSOCK = codeMap.get("ENOTSOCK")!; +export const UV_ETIMEDOUT = codeMap.get("ETIMEDOUT")!; export const UV_UNKNOWN = codeMap.get("UNKNOWN")!; export function errname(errno: number): string { diff --git a/ext/node/polyfills/net.ts b/ext/node/polyfills/net.ts index 48e1d0de8..2b0112519 100644 --- a/ext/node/polyfills/net.ts +++ b/ext/node/polyfills/net.ts @@ -31,6 +31,7 @@ import { isIP, isIPv4, isIPv6, + kReinitializeHandle, normalizedArgsSymbol, } from "ext:deno_node/internal/net.ts"; import { Duplex } from "node:stream"; @@ -50,9 +51,11 @@ import { ERR_SERVER_ALREADY_LISTEN, ERR_SERVER_NOT_RUNNING, ERR_SOCKET_CLOSED, + ERR_SOCKET_CONNECTION_TIMEOUT, errnoException, exceptionWithHostPort, genericNodeError, + NodeAggregateError, uvExceptionWithHostPort, } from "ext:deno_node/internal/errors.ts"; import type { ErrnoException } from "ext:deno_node/internal/errors.ts"; @@ -80,6 +83,7 @@ import { Buffer } from "node:buffer"; import type { LookupOneOptions } from "ext:deno_node/internal/dns/utils.ts"; import { validateAbortSignal, + validateBoolean, validateFunction, validateInt32, validateNumber, @@ -100,13 +104,25 @@ import { ShutdownWrap } from "ext:deno_node/internal_binding/stream_wrap.ts"; import { assert } from "ext:deno_node/_util/asserts.ts"; import { isWindows } from "ext:deno_node/_util/os.ts"; import { ADDRCONFIG, lookup as dnsLookup } from "node:dns"; -import { codeMap } from "ext:deno_node/internal_binding/uv.ts"; +import { + codeMap, + UV_ECANCELED, + UV_ETIMEDOUT, +} from "ext:deno_node/internal_binding/uv.ts"; import { guessHandleType } from "ext:deno_node/internal_binding/util.ts"; import { debuglog } from "ext:deno_node/internal/util/debuglog.ts"; import type { DuplexOptions } from "ext:deno_node/_stream.d.ts"; import type { BufferEncoding } from "ext:deno_node/_global.d.ts"; import type { Abortable } from "ext:deno_node/_events.d.ts"; import { channel } from "node:diagnostics_channel"; +import { primordials } from "ext:core/mod.js"; + +const { + ArrayPrototypeIncludes, + ArrayPrototypePush, + FunctionPrototypeBind, + MathMax, +} = primordials; let debug = debuglog("net", (fn) => { debug = fn; @@ -120,6 +136,9 @@ const kBytesWritten = Symbol("kBytesWritten"); const DEFAULT_IPV4_ADDR = "0.0.0.0"; const DEFAULT_IPV6_ADDR = "::"; +let autoSelectFamilyDefault = true; +let autoSelectFamilyAttemptTimeoutDefault = 250; + type Handle = TCP | Pipe; interface HandleOptions { @@ -214,6 +233,8 @@ interface TcpSocketConnectOptions extends ConnectOptions { hints?: number; family?: number; lookup?: LookupFunction; + autoSelectFamily?: boolean | undefined; + autoSelectFamilyAttemptTimeout?: number | undefined; } interface IpcSocketConnectOptions extends ConnectOptions { @@ -316,12 +337,6 @@ export function _normalizeArgs(args: unknown[]): NormalizedArgs { return arr; } -function _isTCPConnectWrap( - req: TCPConnectWrap | PipeConnectWrap, -): req is TCPConnectWrap { - return "localAddress" in req && "localPort" in req; -} - function _afterConnect( status: number, // deno-lint-ignore no-explicit-any @@ -372,7 +387,7 @@ function _afterConnect( socket.connecting = false; let details; - if (_isTCPConnectWrap(req)) { + if (req.localAddress && req.localPort) { details = req.localAddress + ":" + req.localPort; } @@ -384,7 +399,7 @@ function _afterConnect( details, ); - if (_isTCPConnectWrap(req)) { + if (details) { ex.localAddress = req.localAddress; ex.localPort = req.localPort; } @@ -393,6 +408,107 @@ function _afterConnect( } } +function _createConnectionError(req, status) { + let details; + + if (req.localAddress && req.localPort) { + details = req.localAddress + ":" + req.localPort; + } + + const ex = exceptionWithHostPort( + status, + "connect", + req.address, + req.port, + details, + ); + if (details) { + ex.localAddress = req.localAddress; + ex.localPort = req.localPort; + } + + return ex; +} + +function _afterConnectMultiple( + context, + current, + status, + handle, + req, + readable, + writable, +) { + debug( + "connect/multiple: connection attempt to %s:%s completed with status %s", + req.address, + req.port, + status, + ); + + // Make sure another connection is not spawned + clearTimeout(context[kTimeout]); + + // One of the connection has completed and correctly dispatched but after timeout, ignore this one + if (status === 0 && current !== context.current - 1) { + debug( + "connect/multiple: ignoring successful but timedout connection to %s:%s", + req.address, + req.port, + ); + handle.close(); + return; + } + + const self = context.socket; + + // Some error occurred, add to the list of exceptions + if (status !== 0) { + const ex = _createConnectionError(req, status); + ArrayPrototypePush(context.errors, ex); + + self.emit( + "connectionAttemptFailed", + req.address, + req.port, + req.addressType, + ex, + ); + + // Try the next address, unless we were aborted + if (context.socket.connecting) { + _internalConnectMultiple(context, status === UV_ECANCELED); + } + + return; + } + + _afterConnect(status, self._handle, req, readable, writable); +} + +function _internalConnectMultipleTimeout(context, req, handle) { + debug( + "connect/multiple: connection to %s:%s timed out", + req.address, + req.port, + ); + context.socket.emit( + "connectionAttemptTimeout", + req.address, + req.port, + req.addressType, + ); + + req.oncomplete = undefined; + ArrayPrototypePush(context.errors, _createConnectionError(req, UV_ETIMEDOUT)); + handle.close(); + + // Try the next address, unless we were aborted + if (context.socket.connecting) { + _internalConnectMultiple(context); + } +} + function _checkBindError(err: number, port: number, handle: TCP) { // EADDRINUSE may not be reported until we call `listen()` or `connect()`. // To complicate matters, a failed `bind()` followed by `listen()` or `connect()` @@ -495,6 +611,131 @@ function _internalConnect( } } +function _internalConnectMultiple(context, canceled?: boolean) { + clearTimeout(context[kTimeout]); + const self = context.socket; + + // We were requested to abort. Stop all operations + if (self._aborted) { + return; + } + + // All connections have been tried without success, destroy with error + if (canceled || context.current === context.addresses.length) { + if (context.errors.length === 0) { + self.destroy(new ERR_SOCKET_CONNECTION_TIMEOUT()); + return; + } + + self.destroy(new NodeAggregateError(context.errors)); + return; + } + + assert(self.connecting); + + const current = context.current++; + + if (current > 0) { + self[kReinitializeHandle](new TCP(TCPConstants.SOCKET)); + } + + const { localPort, port, flags } = context; + const { address, family: addressType } = context.addresses[current]; + let localAddress; + let err; + + if (localPort) { + if (addressType === 4) { + localAddress = DEFAULT_IPV4_ADDR; + err = self._handle.bind(localAddress, localPort); + } else { // addressType === 6 + localAddress = DEFAULT_IPV6_ADDR; + err = self._handle.bind6(localAddress, localPort, flags); + } + + debug( + "connect/multiple: binding to localAddress: %s and localPort: %d (addressType: %d)", + localAddress, + localPort, + addressType, + ); + + err = _checkBindError(err, localPort, self._handle); + if (err) { + ArrayPrototypePush( + context.errors, + exceptionWithHostPort(err, "bind", localAddress, localPort), + ); + _internalConnectMultiple(context); + return; + } + } + + debug( + "connect/multiple: attempting to connect to %s:%d (addressType: %d)", + address, + port, + addressType, + ); + self.emit("connectionAttempt", address, port, addressType); + + const req = new TCPConnectWrap(); + req.oncomplete = FunctionPrototypeBind( + _afterConnectMultiple, + undefined, + context, + current, + ); + req.address = address; + req.port = port; + req.localAddress = localAddress; + req.localPort = localPort; + req.addressType = addressType; + + ArrayPrototypePush( + self.autoSelectFamilyAttemptedAddresses, + `${address}:${port}`, + ); + + if (addressType === 4) { + err = self._handle.connect(req, address, port); + } else { + err = self._handle.connect6(req, address, port); + } + + if (err) { + const sockname = self._getsockname(); + let details; + + if (sockname) { + details = sockname.address + ":" + sockname.port; + } + + const ex = exceptionWithHostPort(err, "connect", address, port, details); + ArrayPrototypePush(context.errors, ex); + + self.emit("connectionAttemptFailed", address, port, addressType, ex); + _internalConnectMultiple(context); + return; + } + + if (current < context.addresses.length - 1) { + debug( + "connect/multiple: setting the attempt timeout to %d ms", + context.timeout, + ); + + // If the attempt has not returned an error, start the connection timer + context[kTimeout] = setTimeout( + _internalConnectMultipleTimeout, + context.timeout, + context, + req, + self._handle, + ); + } +} + // Provide a better error message when we call end() as a result // of the other side sending a FIN. The standard "write after end" // is overly vague, and makes it seem like the user's code is to blame. @@ -597,7 +838,7 @@ function _lookupAndConnect( ) { const { localAddress, localPort } = options; const host = options.host || "localhost"; - let { port } = options; + let { port, autoSelectFamilyAttemptTimeout, autoSelectFamily } = options; if (localAddress && !isIP(localAddress)) { throw new ERR_INVALID_IP_ADDRESS(localAddress); @@ -621,6 +862,22 @@ function _lookupAndConnect( port |= 0; + if (autoSelectFamily != null) { + validateBoolean(autoSelectFamily, "options.autoSelectFamily"); + } else { + autoSelectFamily = autoSelectFamilyDefault; + } + + if (autoSelectFamilyAttemptTimeout !== undefined) { + validateInt32(autoSelectFamilyAttemptTimeout); + + if (autoSelectFamilyAttemptTimeout < 10) { + autoSelectFamilyAttemptTimeout = 10; + } + } else { + autoSelectFamilyAttemptTimeout = autoSelectFamilyAttemptTimeoutDefault; + } + // If host is an IP, skip performing a lookup const addressType = isIP(host); if (addressType) { @@ -649,6 +906,7 @@ function _lookupAndConnect( const dnsOpts = { family: options.family, hints: options.hints || 0, + all: false, }; if ( @@ -665,6 +923,31 @@ function _lookupAndConnect( self._host = host; const lookup = options.lookup || dnsLookup; + if ( + dnsOpts.family !== 4 && dnsOpts.family !== 6 && !localAddress && + autoSelectFamily + ) { + debug("connect: autodetecting"); + + dnsOpts.all = true; + defaultTriggerAsyncIdScope(self[asyncIdSymbol], function () { + _lookupAndConnectMultiple( + self, + asyncIdSymbol, + lookup, + host, + options, + dnsOpts, + port, + localAddress, + localPort, + autoSelectFamilyAttemptTimeout, + ); + }); + + return; + } + defaultTriggerAsyncIdScope(self[asyncIdSymbol], function () { lookup( host, @@ -719,6 +1002,143 @@ function _lookupAndConnect( }); } +function _lookupAndConnectMultiple( + self: Socket, + asyncIdSymbol: number, + // deno-lint-ignore no-explicit-any + lookup: any, + host: string, + options: TcpSocketConnectOptions, + dnsopts, + port: number, + localAddress: string, + localPort: number, + timeout: number | undefined, +) { + defaultTriggerAsyncIdScope(self[asyncIdSymbol], function emitLookup() { + lookup(host, dnsopts, function emitLookup(err, addresses) { + // It's possible we were destroyed while looking this up. + // XXX it would be great if we could cancel the promise returned by + // the look up. + if (!self.connecting) { + return; + } else if (err) { + self.emit("lookup", err, undefined, undefined, host); + + // net.createConnection() creates a net.Socket object and immediately + // calls net.Socket.connect() on it (that's us). There are no event + // listeners registered yet so defer the error event to the next tick. + nextTick(_connectErrorNT, self, err); + return; + } + + // Filter addresses by only keeping the one which are either IPv4 or IPV6. + // The first valid address determines which group has preference on the + // alternate family sorting which happens later. + const validAddresses = [[], []]; + const validIps = [[], []]; + let destinations; + for (let i = 0, l = addresses.length; i < l; i++) { + const address = addresses[i]; + const { address: ip, family: addressType } = address; + self.emit("lookup", err, ip, addressType, host); + // It's possible we were destroyed while looking this up. + if (!self.connecting) { + return; + } + if (isIP(ip) && (addressType === 4 || addressType === 6)) { + destinations ||= addressType === 6 ? { 6: 0, 4: 1 } : { 4: 0, 6: 1 }; + + const destination = destinations[addressType]; + + // Only try an address once + if (!ArrayPrototypeIncludes(validIps[destination], ip)) { + ArrayPrototypePush(validAddresses[destination], address); + ArrayPrototypePush(validIps[destination], ip); + } + } + } + + // When no AAAA or A records are available, fail on the first one + if (!validAddresses[0].length && !validAddresses[1].length) { + const { address: firstIp, family: firstAddressType } = addresses[0]; + + if (!isIP(firstIp)) { + err = new ERR_INVALID_IP_ADDRESS(firstIp); + nextTick(_connectErrorNT, self, err); + } else if (firstAddressType !== 4 && firstAddressType !== 6) { + err = new ERR_INVALID_ADDRESS_FAMILY( + firstAddressType, + options.host, + options.port, + ); + nextTick(_connectErrorNT, self, err); + } + + return; + } + + // Sort addresses alternating families + const toAttempt = []; + for ( + let i = 0, + l = MathMax(validAddresses[0].length, validAddresses[1].length); + i < l; + i++ + ) { + if (i in validAddresses[0]) { + ArrayPrototypePush(toAttempt, validAddresses[0][i]); + } + if (i in validAddresses[1]) { + ArrayPrototypePush(toAttempt, validAddresses[1][i]); + } + } + + if (toAttempt.length === 1) { + debug( + "connect/multiple: only one address found, switching back to single connection", + ); + const { address: ip, family: addressType } = toAttempt[0]; + + self._unrefTimer(); + defaultTriggerAsyncIdScope( + self[asyncIdSymbol], + _internalConnect, + self, + ip, + port, + addressType, + localAddress, + localPort, + ); + + return; + } + + self.autoSelectFamilyAttemptedAddresses = []; + debug("connect/multiple: will try the following addresses", toAttempt); + + const context = { + socket: self, + addresses: toAttempt, + current: 0, + port, + localPort, + timeout, + [kTimeout]: null, + errors: [], + }; + + self._unrefTimer(); + defaultTriggerAsyncIdScope( + self[asyncIdSymbol], + _internalConnectMultiple, + context, + ); + }); + }); +} + function _afterShutdown(this: ShutdownWrap) { // deno-lint-ignore no-explicit-any const self: any = this.handle[ownerSymbol]; @@ -777,6 +1197,7 @@ export class Socket extends Duplex { _host: string | null = null; // deno-lint-ignore no-explicit-any _parent: any = null; + autoSelectFamilyAttemptedAddresses: AddressInfo[] | undefined = undefined; constructor(options: SocketOptions | number) { if (typeof options === "number") { @@ -1546,6 +1967,16 @@ export class Socket extends Duplex { set _handle(v: Handle | null) { this[kHandle] = v; } + + // deno-lint-ignore no-explicit-any + [kReinitializeHandle](handle: any) { + this._handle?.close(); + + this._handle = handle; + this._handle[ownerSymbol] = this; + + _initSocketHandle(this); + } } export const Stream = Socket; @@ -1593,6 +2024,33 @@ export function connect(...args: unknown[]) { export const createConnection = connect; +/** https://docs.deno.com/api/node/net/#namespace_getdefaultautoselectfamily */ +export function getDefaultAutoSelectFamily() { + return autoSelectFamilyDefault; +} + +/** https://docs.deno.com/api/node/net/#namespace_setdefaultautoselectfamily */ +export function setDefaultAutoSelectFamily(value: boolean) { + validateBoolean(value, "value"); + autoSelectFamilyDefault = value; +} + +/** https://docs.deno.com/api/node/net/#namespace_getdefaultautoselectfamilyattempttimeout */ +export function getDefaultAutoSelectFamilyAttemptTimeout() { + return autoSelectFamilyAttemptTimeoutDefault; +} + +/** https://docs.deno.com/api/node/net/#namespace_setdefaultautoselectfamilyattempttimeout */ +export function setDefaultAutoSelectFamilyAttemptTimeout(value: number) { + validateInt32(value, "value", 1); + + if (value < 10) { + value = 10; + } + + autoSelectFamilyAttemptTimeoutDefault = value; +} + export interface ListenOptions extends Abortable { fd?: number; port?: number | undefined; @@ -2478,15 +2936,19 @@ export { BlockList, isIP, isIPv4, isIPv6, SocketAddress }; export default { _createServerHandle, _normalizeArgs, - isIP, - isIPv4, - isIPv6, BlockList, - SocketAddress, connect, createConnection, createServer, + getDefaultAutoSelectFamily, + getDefaultAutoSelectFamilyAttemptTimeout, + isIP, + isIPv4, + isIPv6, Server, + setDefaultAutoSelectFamily, + setDefaultAutoSelectFamilyAttemptTimeout, Socket, + SocketAddress, Stream, }; -- cgit v1.2.3 From 43812ee8ff0eb2584c7beb18639da14d96d06817 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 13 Nov 2024 08:02:09 +0530 Subject: fix(ext/node): process.getBuiltinModule (#26833) Closes https://github.com/denoland/deno/issues/26832 --- ext/node/polyfills/01_require.js | 20 +++++++++++++++++++- ext/node/polyfills/process.ts | 14 ++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/01_require.js b/ext/node/polyfills/01_require.js index d818bb572..0d267ca44 100644 --- a/ext/node/polyfills/01_require.js +++ b/ext/node/polyfills/01_require.js @@ -1233,6 +1233,24 @@ function isBuiltin(moduleName) { !StringPrototypeStartsWith(moduleName, "internal/"); } +function getBuiltinModule(id) { + if (!isBuiltin(id)) { + return undefined; + } + + if (StringPrototypeStartsWith(id, "node:")) { + // Slice 'node:' prefix + id = StringPrototypeSlice(id, 5); + } + + const mod = loadNativeModule(id, id); + if (mod) { + return mod.exports; + } + + return undefined; +} + Module.isBuiltin = isBuiltin; Module.createRequire = createRequire; @@ -1327,7 +1345,7 @@ export function register(_specifier, _parentUrl, _options) { return undefined; } -export { builtinModules, createRequire, isBuiltin, Module }; +export { builtinModules, createRequire, getBuiltinModule, isBuiltin, Module }; export const _cache = Module._cache; export const _extensions = Module._extensions; export const _findPath = Module._findPath; diff --git a/ext/node/polyfills/process.ts b/ext/node/polyfills/process.ts index 2605fa6d1..bf626e410 100644 --- a/ext/node/polyfills/process.ts +++ b/ext/node/polyfills/process.ts @@ -15,7 +15,7 @@ import { import { warnNotImplemented } from "ext:deno_node/_utils.ts"; import { EventEmitter } from "node:events"; -import Module from "node:module"; +import Module, { getBuiltinModule } from "node:module"; import { report } from "ext:deno_node/internal/process/report.ts"; import { validateString } from "ext:deno_node/internal/validators.mjs"; import { @@ -38,7 +38,15 @@ import { versions, } from "ext:deno_node/_process/process.ts"; import { _exiting } from "ext:deno_node/_process/exiting.ts"; -export { _nextTick as nextTick, chdir, cwd, env, version, versions }; +export { + _nextTick as nextTick, + chdir, + cwd, + env, + getBuiltinModule, + version, + versions, +}; import { createWritableStdioStream, initStdin, @@ -728,6 +736,8 @@ Process.prototype.getegid = getegid; /** This method is removed on Windows */ Process.prototype.geteuid = geteuid; +Process.prototype.getBuiltinModule = getBuiltinModule; + // TODO(kt3k): Implement this when we added -e option to node compat mode Process.prototype._eval = undefined; -- cgit v1.2.3 From 7becd83a3828b35331d0fcb82c64146e520f154b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Czerniawski?= <33061335+lczerniawski@users.noreply.github.com> Date: Wed, 13 Nov 2024 05:35:04 +0100 Subject: feat(ext/fs): add ctime to Deno.stats and use it in node compat layer (#24801) This PR fixes #24453, by introducing a ctime (using ctime for UNIX and ChangeTime for Windows) to Deno.stats. Co-authored-by: Yoshiya Hinosawa --- ext/node/polyfills/_fs/_fs_stat.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/_fs/_fs_stat.ts b/ext/node/polyfills/_fs/_fs_stat.ts index d00c81ffb..507cb05ea 100644 --- a/ext/node/polyfills/_fs/_fs_stat.ts +++ b/ext/node/polyfills/_fs/_fs_stat.ts @@ -290,8 +290,8 @@ export function convertFileInfoToStats(origin: Deno.FileInfo): Stats { isFIFO: () => false, isCharacterDevice: () => false, isSocket: () => false, - ctime: origin.mtime, - ctimeMs: origin.mtime?.getTime() || null, + ctime: origin.ctime, + ctimeMs: origin.ctime?.getTime() || null, }); return stats; @@ -336,9 +336,9 @@ export function convertFileInfoToBigIntStats( isFIFO: () => false, isCharacterDevice: () => false, isSocket: () => false, - ctime: origin.mtime, - ctimeMs: origin.mtime ? BigInt(origin.mtime.getTime()) : null, - ctimeNs: origin.mtime ? BigInt(origin.mtime.getTime()) * 1000000n : null, + ctime: origin.ctime, + ctimeMs: origin.ctime ? BigInt(origin.ctime.getTime()) : null, + ctimeNs: origin.ctime ? BigInt(origin.ctime.getTime()) * 1000000n : null, }); return stats; } -- cgit v1.2.3 From 7d9ba09f5a4464072476b8992e43f5e5c30bde3a Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 13 Nov 2024 19:47:01 +0530 Subject: fix(ext/node): use ERR_NOT_IMPLEMENTED for notImplemented (#26853) --- ext/node/polyfills/_utils.ts | 4 ++-- ext/node/polyfills/internal/errors.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/_utils.ts b/ext/node/polyfills/_utils.ts index b50c113e1..79d84e00f 100644 --- a/ext/node/polyfills/_utils.ts +++ b/ext/node/polyfills/_utils.ts @@ -17,6 +17,7 @@ const { import { TextDecoder, TextEncoder } from "ext:deno_web/08_text_encoding.js"; import { errorMap } from "ext:deno_node/internal_binding/uv.ts"; import { codes } from "ext:deno_node/internal/error_codes.ts"; +import { ERR_NOT_IMPLEMENTED } from "ext:deno_node/internal/errors.ts"; export type BinaryEncodings = "binary"; @@ -34,8 +35,7 @@ export type TextEncodings = export type Encodings = BinaryEncodings | TextEncodings; export function notImplemented(msg: string): never { - const message = msg ? `Not implemented: ${msg}` : "Not implemented"; - throw new Error(message); + throw new ERR_NOT_IMPLEMENTED(msg); } export function warnNotImplemented(msg?: string) { diff --git a/ext/node/polyfills/internal/errors.ts b/ext/node/polyfills/internal/errors.ts index 962ca86e9..61b53fa96 100644 --- a/ext/node/polyfills/internal/errors.ts +++ b/ext/node/polyfills/internal/errors.ts @@ -2390,6 +2390,15 @@ export class ERR_INVALID_RETURN_VALUE extends NodeTypeError { } } +export class ERR_NOT_IMPLEMENTED extends NodeError { + constructor(message?: string) { + super( + "ERR_NOT_IMPLEMENTED", + message ? `Not implemented: ${message}` : "Not implemented", + ); + } +} + export class ERR_INVALID_URL extends NodeTypeError { input: string; constructor(input: string) { @@ -2862,6 +2871,7 @@ export default { ERR_INVALID_SYNC_FORK_INPUT, ERR_INVALID_THIS, ERR_INVALID_TUPLE, + ERR_NOT_IMPLEMENTED, ERR_INVALID_URI, ERR_INVALID_URL, ERR_INVALID_URL_SCHEME, -- cgit v1.2.3 From 6a4c6d83bacf5f03628a494778a30bce970f7cbc Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 13 Nov 2024 20:07:45 +0530 Subject: fix(ext/node): zlib.crc32() (#26856) Fixes https://github.com/denoland/deno/issues/26845 --- ext/node/polyfills/zlib.ts | 53 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/zlib.ts b/ext/node/polyfills/zlib.ts index 3fe5f8bbd..6e5d02b5b 100644 --- a/ext/node/polyfills/zlib.ts +++ b/ext/node/polyfills/zlib.ts @@ -40,6 +40,58 @@ import { createBrotliCompress, createBrotliDecompress, } from "ext:deno_node/_brotli.js"; +import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts"; +import { validateUint32 } from "ext:deno_node/internal/validators.mjs"; +import { op_zlib_crc32 } from "ext:core/ops"; +import { core, primordials } from "ext:core/mod.js"; +import { TextEncoder } from "ext:deno_web/08_text_encoding.js"; +const { + Uint8Array, + TypedArrayPrototypeGetBuffer, + TypedArrayPrototypeGetByteLength, + TypedArrayPrototypeGetByteOffset, + DataViewPrototypeGetBuffer, + DataViewPrototypeGetByteLength, + DataViewPrototypeGetByteOffset, +} = primordials; +const { isTypedArray, isDataView } = core; + +const enc = new TextEncoder(); +const toU8 = (input) => { + if (typeof input === "string") { + return enc.encode(input); + } + + if (isTypedArray(input)) { + return new Uint8Array( + TypedArrayPrototypeGetBuffer(input), + TypedArrayPrototypeGetByteOffset(input), + TypedArrayPrototypeGetByteLength(input), + ); + } else if (isDataView(input)) { + return new Uint8Array( + DataViewPrototypeGetBuffer(input), + DataViewPrototypeGetByteOffset(input), + DataViewPrototypeGetByteLength(input), + ); + } + + return input; +}; + +export function crc32(data, value = 0) { + if (typeof data !== "string" && !isArrayBufferView(data)) { + throw new ERR_INVALID_ARG_TYPE("data", [ + "Buffer", + "TypedArray", + "DataView", + "string", + ], data); + } + validateUint32(value, "value"); + + return op_zlib_crc32(toU8(data), value); +} export class Options { constructor() { @@ -87,6 +139,7 @@ export default { BrotliOptions, codes, constants, + crc32, createBrotliCompress, createBrotliDecompress, createDeflate, -- cgit v1.2.3 From f091d1ad69b4e5217ae3272b641171781a372c4f Mon Sep 17 00:00:00 2001 From: David Sherret Date: Wed, 13 Nov 2024 10:10:09 -0500 Subject: feat(node): stabilize detecting if CJS via `"type": "commonjs"` in a package.json (#26439) This will respect `"type": "commonjs"` in a package.json to determine if `.js`/`.jsx`/`.ts`/.tsx` files are CJS or ESM. If the file is found to be ESM it will be loaded as ESM though. --- ext/node/polyfills/01_require.js | 31 ++++--------------------------- ext/node/polyfills/process.ts | 2 +- 2 files changed, 5 insertions(+), 28 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/01_require.js b/ext/node/polyfills/01_require.js index 0d267ca44..083d4e49b 100644 --- a/ext/node/polyfills/01_require.js +++ b/ext/node/polyfills/01_require.js @@ -11,6 +11,7 @@ import { op_require_can_parse_as_esm, op_require_init_paths, op_require_is_deno_dir_package, + op_require_is_maybe_cjs, op_require_is_request_relative, op_require_node_module_paths, op_require_package_imports_resolve, @@ -19,7 +20,6 @@ import { op_require_path_is_absolute, op_require_path_resolve, op_require_proxy_path, - op_require_read_closest_package_json, op_require_read_file, op_require_read_package_scope, op_require_real_path, @@ -1060,36 +1060,13 @@ Module.prototype._compile = function (content, filename, format) { return result; }; -Module._extensions[".js"] = function (module, filename) { - const content = op_require_read_file(filename); - - let format; - if (StringPrototypeEndsWith(filename, ".js")) { - const pkg = op_require_read_closest_package_json(filename); - if (pkg?.type === "module") { - format = "module"; - } else if (pkg?.type === "commonjs") { - format = "commonjs"; - } - } - - module._compile(content, filename, format); -}; - -Module._extensions[".ts"] = +Module._extensions[".js"] = + Module._extensions[".ts"] = Module._extensions[".jsx"] = Module._extensions[".tsx"] = function (module, filename) { const content = op_require_read_file(filename); - - let format; - const pkg = op_require_read_closest_package_json(filename); - if (pkg?.type === "module") { - format = "module"; - } else if (pkg?.type === "commonjs") { - format = "commonjs"; - } - + const format = op_require_is_maybe_cjs(filename) ? undefined : "module"; module._compile(content, filename, format); }; diff --git a/ext/node/polyfills/process.ts b/ext/node/polyfills/process.ts index bf626e410..647376d5c 100644 --- a/ext/node/polyfills/process.ts +++ b/ext/node/polyfills/process.ts @@ -919,7 +919,7 @@ Object.defineProperty(argv, "1", { if (Deno.mainModule?.startsWith("file:")) { return pathFromURL(new URL(Deno.mainModule)); } else { - return join(Deno.cwd(), "$deno$node.js"); + return join(Deno.cwd(), "$deno$node.mjs"); } }, }); -- cgit v1.2.3 From 8d2960d7ccb756de4260a642d960cd01eaaa2478 Mon Sep 17 00:00:00 2001 From: /usr/bin/cat <109351887+cu8code@users.noreply.github.com> Date: Sat, 16 Nov 2024 20:31:19 +0530 Subject: fix(ext/node): New async setInterval function to improve the nodejs compatibility (#26703) Closes #26499 --- ext/node/polyfills/timers.ts | 88 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/timers.ts b/ext/node/polyfills/timers.ts index 02f69466e..e826416ed 100644 --- a/ext/node/polyfills/timers.ts +++ b/ext/node/polyfills/timers.ts @@ -15,10 +15,16 @@ import { setUnrefTimeout, Timeout, } from "ext:deno_node/internal/timers.mjs"; -import { validateFunction } from "ext:deno_node/internal/validators.mjs"; +import { + validateAbortSignal, + validateBoolean, + validateFunction, + validateObject, +} from "ext:deno_node/internal/validators.mjs"; import { promisify } from "ext:deno_node/internal/util.mjs"; export { setUnrefTimeout } from "ext:deno_node/internal/timers.mjs"; import * as timers from "ext:deno_web/02_timers.js"; +import { AbortError } from "ext:deno_node/internal/errors.ts"; const clearTimeout_ = timers.clearTimeout; const clearInterval_ = timers.clearInterval; @@ -89,10 +95,88 @@ export function clearImmediate(immediate: Immediate) { clearTimeout_(immediate._immediateId); } +async function* setIntervalAsync( + after: number, + value: number, + options: { signal?: AbortSignal; ref?: boolean } = { __proto__: null }, +) { + validateObject(options, "options"); + + if (typeof options?.signal !== "undefined") { + validateAbortSignal(options.signal, "options.signal"); + } + + if (typeof options?.ref !== "undefined") { + validateBoolean(options.ref, "options.ref"); + } + + const { signal, ref = true } = options; + + if (signal?.aborted) { + throw new AbortError(undefined, { cause: signal?.reason }); + } + + let onCancel: (() => void) | undefined = undefined; + let interval: Timeout | undefined = undefined; + try { + let notYielded = 0; + let callback: ((value?: object) => void) | undefined = undefined; + let rejectCallback: ((message?: string) => void) | undefined = undefined; + interval = new Timeout( + () => { + notYielded++; + if (callback) { + callback(); + callback = undefined; + rejectCallback = undefined; + } + }, + after, + [], + true, + ref, + ); + if (signal) { + onCancel = () => { + clearInterval(interval); + if (rejectCallback) { + rejectCallback(signal.reason); + callback = undefined; + rejectCallback = undefined; + } + }; + signal.addEventListener("abort", onCancel, { once: true }); + } + while (!signal?.aborted) { + if (notYielded === 0) { + await new Promise((resolve: () => void, reject: () => void) => { + callback = resolve; + rejectCallback = reject; + }); + } + for (; notYielded > 0; notYielded--) { + yield value; + } + } + } catch (error) { + if (signal?.aborted) { + throw new AbortError(undefined, { cause: signal?.reason }); + } + throw error; + } finally { + if (interval) { + clearInterval(interval); + } + if (onCancel) { + signal?.removeEventListener("abort", onCancel); + } + } +} + export const promises = { setTimeout: promisify(setTimeout), setImmediate: promisify(setImmediate), - setInterval: promisify(setInterval), + setInterval: setIntervalAsync, }; promises.scheduler = { -- cgit v1.2.3 From df1d36324ffd4f687c406e412f9255bf7a9d8a61 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Tue, 19 Nov 2024 01:39:40 +0100 Subject: fix(node/crypto): support promisify on generateKeyPair (#26913) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calling `promisify(generateKeyPair)` didn't work as expected. It requires a custom promisify implementation. This was easy to fix thanks to the excellent debugging investigation in https://github.com/denoland/deno/issues/26910 Fixes https://github.com/denoland/deno/issues/26910 Co-authored-by: Bartek Iwańczuk --- ext/node/polyfills/internal/crypto/keygen.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/internal/crypto/keygen.ts b/ext/node/polyfills/internal/crypto/keygen.ts index 44bfd8327..b023ab106 100644 --- a/ext/node/polyfills/internal/crypto/keygen.ts +++ b/ext/node/polyfills/internal/crypto/keygen.ts @@ -30,6 +30,7 @@ import { import { Buffer } from "node:buffer"; import { KeyFormat, KeyType } from "ext:deno_node/internal/crypto/types.ts"; import process from "node:process"; +import { promisify } from "node:util"; import { op_node_generate_dh_group_key, @@ -570,7 +571,15 @@ export function generateKeyPair( privateKey: any, ) => void, ) { - createJob(kAsync, type, options).then((pair) => { + _generateKeyPair(type, options) + .then( + (res) => callback(null, res.publicKey, res.privateKey), + (err) => callback(err, null, null), + ); +} + +function _generateKeyPair(type: string, options: unknown) { + return createJob(kAsync, type, options).then((pair) => { const privateKeyHandle = op_node_get_private_key_from_pair(pair); const publicKeyHandle = op_node_get_public_key_from_pair(pair); @@ -589,12 +598,15 @@ export function generateKeyPair( } } - callback(null, publicKey, privateKey); - }).catch((err) => { - callback(err, null, null); + return { publicKey, privateKey }; }); } +Object.defineProperty(generateKeyPair, promisify.custom, { + enumerable: false, + value: _generateKeyPair, +}); + export interface KeyPairKeyObjectResult { publicKey: KeyObject; privateKey: KeyObject; -- cgit v1.2.3 From 069bc15030225393f7d05521505316066464bdbd Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Tue, 19 Nov 2024 16:49:25 +0530 Subject: feat(ext/node): perf_hooks.monitorEventLoopDelay() (#26905) Fixes https://github.com/denoland/deno/issues/20961 Depends on https://github.com/denoland/deno_core/pull/965 and https://github.com/denoland/deno_core/pull/966 --- ext/node/polyfills/perf_hooks.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'ext/node/polyfills') diff --git a/ext/node/polyfills/perf_hooks.ts b/ext/node/polyfills/perf_hooks.ts index d92b925b5..ec76b3ce2 100644 --- a/ext/node/polyfills/perf_hooks.ts +++ b/ext/node/polyfills/perf_hooks.ts @@ -8,6 +8,7 @@ import { performance as shimPerformance, PerformanceEntry, } from "ext:deno_web/15_performance.js"; +import { EldHistogram } from "ext:core/ops"; class PerformanceObserver { static supportedEntryTypes: string[] = []; @@ -89,10 +90,11 @@ const performance: ) => shimPerformance.dispatchEvent(...args), }; -const monitorEventLoopDelay = () => - notImplemented( - "monitorEventLoopDelay from performance", - ); +function monitorEventLoopDelay(options = {}) { + const { resolution = 10 } = options; + + return new EldHistogram(resolution); +} export default { performance, -- cgit v1.2.3