From 9da84bde545e390432e3e42f6b9462a946de9ea9 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Tue, 6 Aug 2024 11:24:00 -0400 Subject: [PATCH] add file class to node:buffer --- package.json | 2 +- pnpm-lock.yaml | 8 +- src/node/buffer.ts | 5 + src/node/internal/crypto_random.ts | 26 ++--- src/node/internal/web_crypto.d.ts | 8 -- src/workerd/api/blob.c++ | 4 + .../api/node/tests/buffer-nodejs-test.js | 102 ++++++++++++++++++ 7 files changed, 130 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 88ce2c2c2b9..8a1fef5b9ef 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "devDependencies": { "@bazel/bazelisk": "~1.19.0", "@types/debug": "^4.1.10", - "@types/node": "^20.11.6", + "@types/node": "^20.14.8", "@types/prettier": "^2.7.1", "@typescript-eslint/eslint-plugin": "^7.3.1", "@typescript-eslint/parser": "^7.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94c815f72de..14cd9294f01 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,8 +26,8 @@ devDependencies: specifier: ^4.1.10 version: 4.1.10 '@types/node': - specifier: ^20.11.6 - version: 20.11.6 + specifier: ^20.14.8 + version: 20.14.14 '@types/prettier': specifier: ^2.7.1 version: 2.7.1 @@ -190,8 +190,8 @@ packages: resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} dev: true - /@types/node@20.11.6: - resolution: {integrity: sha512-+EOokTnksGVgip2PbYbr3xnR7kZigh4LbybAfBAw5BpnQ+FqBYUsvCEjYd70IXKlbohQ64mzEYmMtlWUY8q//Q==} + /@types/node@20.14.14: + resolution: {integrity: sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==} dependencies: undici-types: 5.26.5 dev: true diff --git a/src/node/buffer.ts b/src/node/buffer.ts index d5e5b9a1e82..55c230f1448 100644 --- a/src/node/buffer.ts +++ b/src/node/buffer.ts @@ -19,6 +19,8 @@ const atob = globalThis.atob; const btoa = globalThis.btoa; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const Blob = globalThis.Blob; +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment +const File = globalThis.File; export { atob, @@ -28,6 +30,7 @@ export { kStringMaxLength, Blob, Buffer, + File, SlowBuffer, isAscii, isUtf8, @@ -45,6 +48,8 @@ export default { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment Blob, Buffer, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + File, SlowBuffer, isAscii, isUtf8, diff --git a/src/node/internal/crypto_random.ts b/src/node/internal/crypto_random.ts index 459fe29917d..bdcaaf2e36b 100644 --- a/src/node/internal/crypto_random.ts +++ b/src/node/internal/crypto_random.ts @@ -55,6 +55,8 @@ import { arrayBufferToUnsignedBigInt, } from 'node-internal:crypto_util'; +type BufferLike = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | BigInt64Array | BigUint64Array; + export type RandomBytesCallback = (err: any|null, buffer: Uint8Array) => void; export function randomBytes(size: number, callback: RandomBytesCallback): void; export function randomBytes(size: number): Uint8Array; @@ -70,7 +72,7 @@ export function randomBytes(size: number, callback?: RandomBytesCallback) : Uint } export function randomFillSync( - buffer: ArrayBufferView|ArrayBuffer, + buffer: BufferLike, offset?: number, size?: number) { if (!isAnyArrayBuffer(buffer) && !isArrayBufferView(buffer)) { @@ -79,9 +81,9 @@ export function randomFillSync( 'DataView', 'ArrayBuffer', 'SharedArrayBuffer' - ],buffer); + ], buffer); } - const maxLength = (buffer as Uint8Array).length; + const maxLength = buffer.length; if (offset !== undefined) { validateInteger(offset!, 'offset', 0, kMaxLength); } else offset = 0; @@ -89,23 +91,23 @@ export function randomFillSync( validateInteger(size!, 'size', 0, maxLength - offset); } else size = maxLength; if (isAnyArrayBuffer(buffer)) { - buffer = Buffer.from(buffer as ArrayBuffer); + buffer = Buffer.from(buffer); } - buffer = (buffer as Buffer).subarray(offset, offset + size); - return crypto.getRandomValues(buffer as ArrayBufferView); + buffer = buffer.subarray(offset, offset + size); + return crypto.getRandomValues(buffer); } export type RandomFillCallback = (err: any|null, buf?: ArrayBufferView|ArrayBuffer) => void; -export function randomFill(buffer: ArrayBufferView|ArrayBuffer, +export function randomFill(buffer: BufferLike, callback?: RandomFillCallback) : void; -export function randomFill(buffer: ArrayBufferView|ArrayBuffer, +export function randomFill(buffer: BufferLike, offset: number, callback?: RandomFillCallback) : void; - export function randomFill(buffer: ArrayBufferView|ArrayBuffer, + export function randomFill(buffer: BufferLike, offset: number, size: number, callback?: RandomFillCallback) : void; -export function randomFill(buffer: ArrayBufferView|ArrayBuffer, +export function randomFill(buffer: BufferLike, offsetOrCallback?: number|RandomFillCallback, sizeOrCallback?: number|RandomFillCallback, callback?: RandomFillCallback) { @@ -115,12 +117,12 @@ export function randomFill(buffer: ArrayBufferView|ArrayBuffer, 'DataView', 'ArrayBuffer', 'SharedArrayBuffer' - ],buffer); + ], buffer); } let offset = 0; let size = 0; - const maxLength = (buffer as Uint8Array).length; + const maxLength = buffer.length; if (typeof callback === 'function') { validateInteger(offsetOrCallback, 'offset', 0, maxLength); offset = offsetOrCallback as number; diff --git a/src/node/internal/web_crypto.d.ts b/src/node/internal/web_crypto.d.ts index 3c1b43c1b92..ea465710acc 100644 --- a/src/node/internal/web_crypto.d.ts +++ b/src/node/internal/web_crypto.d.ts @@ -1,12 +1,4 @@ // Just enough of web crypto api to write the code in node-compat - -declare namespace crypto { - function getRandomValues(array: T): T; - function randomUUID(): string; - - const subtle: SubtleCrypto; -} - type SubtleCrypto = unknown; type CryptoKey = unknown; diff --git a/src/workerd/api/blob.c++ b/src/workerd/api/blob.c++ index 1849a4a52d7..c34f9493a59 100644 --- a/src/workerd/api/blob.c++ +++ b/src/workerd/api/blob.c++ @@ -305,6 +305,10 @@ jsg::Ref File::constructor(jsg::Lock& js, jsg::Optional bits, double lastModified; KJ_IF_SOME(m, maybeLastModified) { lastModified = m; + + if (kj::isNaN(lastModified)) { + lastModified = 0; + } } else { lastModified = dateNow(); } diff --git a/src/workerd/api/node/tests/buffer-nodejs-test.js b/src/workerd/api/node/tests/buffer-nodejs-test.js index e0547414bdb..0dd6896b31d 100644 --- a/src/workerd/api/node/tests/buffer-nodejs-test.js +++ b/src/workerd/api/node/tests/buffer-nodejs-test.js @@ -42,6 +42,7 @@ import { isAscii, isUtf8, transcode, + File, } from 'node:buffer'; import * as buffer from 'node:buffer'; @@ -5792,3 +5793,104 @@ export const transcodeTest = { } } }; + +// Tests are taken from Node.js +// https://github.com/nodejs/node/blob/main/test/parallel/test-file.js +export const fileTest = { + test() { + throws(() => new File(), TypeError); + throws(() => new File([]), TypeError); + throws(() => File.prototype.name, TypeError); + throws(() => File.prototype.lastModified, TypeError); + + { + const keys = Object.keys(File.prototype).sort(); + deepStrictEqual(keys, ['lastModified', 'name']); + } + + { + const file = new File([], 'dummy.txt.exe'); + strictEqual(file.name, 'dummy.txt.exe'); + strictEqual(file.size, 0); + strictEqual(typeof file.lastModified, 'number'); + ok(file.lastModified <= Date.now()); + } + + { + const toPrimitive = { + [Symbol.toPrimitive]() { + return 'NaN'; + } + }; + + const invalidLastModified = [ + null, + 'string', + false, + toPrimitive, + ]; + + for (const lastModified of invalidLastModified) { + const file = new File([], '', { lastModified }); + strictEqual(file.lastModified, 0); + } + } + + { + const file = new File([], '', { lastModified: undefined }); + notStrictEqual(file.lastModified, 0); + } + + { + const toPrimitive = { + [Symbol.toPrimitive]() { + throw new TypeError('boom'); + } + }; + + const throwValues = [ + BigInt(3n), + toPrimitive, + ]; + + for (const lastModified of throwValues) { + throws(() => new File([], '', { lastModified }), TypeError); + } + } + + { + const valid = [ + { + [Symbol.toPrimitive]() { + return 10; + } + }, + new Number(10), + 10, + ]; + + for (const lastModified of valid) { + strictEqual(new File([], '', { lastModified }).lastModified, 10); + } + } + + { + function MyClass() {} + MyClass.prototype.lastModified = 10; + + const file = new File([], '', new MyClass()); + strictEqual(file.lastModified, 10); + } + + { + let counter = 0; + new File([], '', { + get lastModified() { + counter++; + return 10; + } + }); + strictEqual(counter, 1); + } + } +}