From 1c15fcd87bee46807b5390bc2f9444e08b9a9b70 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Fri, 21 Jan 2022 00:00:17 +0800 Subject: [PATCH 01/14] fs: make promises/write and writeSync params optional This change allows passing objects as "named parameters" and prohibits passing objects with own toString property as strings. Fixes: https://github.com/nodejs/node/issues/41666 --- doc/api/fs.md | 72 +++++++++++++++++++++++++++---------- lib/fs.js | 22 +++++++++--- lib/internal/fs/promises.js | 16 +++++++-- lib/internal/fs/utils.js | 11 ++++++ 4 files changed, 95 insertions(+), 26 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index 66363f11e94bb9..a1a7ff72d1a5f5 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -601,7 +601,7 @@ changes: buffers anymore. --> -* `buffer` {Buffer|TypedArray|DataView|string|Object} +* `buffer` {Buffer|TypedArray|DataView} * `offset` {integer} The start position from within `buffer` where the data to write begins. **Default:** `0` * `length` {integer} The number of bytes from `buffer` to write. **Default:** @@ -614,13 +614,10 @@ changes: Write `buffer` to the file. -If `buffer` is a plain object, it must have an own (not inherited) `toString` -function property. - The promise is resolved with an object containing two properties: * `bytesWritten` {integer} the number of bytes written -* `buffer` {Buffer|TypedArray|DataView|string|Object} a reference to the +* `buffer` {Buffer|TypedArray|DataView} a reference to the `buffer` written. It is unsafe to use `filehandle.write()` multiple times on the same file @@ -631,11 +628,34 @@ On Linux, positional writes do not work when the file is opened in append mode. The kernel ignores the position argument and always appends the data to the end of the file. +#### `filehandle.write(buffer, options)` + + + +* `buffer` {Buffer|TypedArray|DataView} +* `options` {Object} + * `offset` {integer} **Default:** `0` + * `length` {integer} **Default:** `buffer.byteLength` + * `position` {integer} **Default:** `null` +* Returns: {Promise} + +Write `buffer` to the file. + +Similar to the above `filehandle.write` function, this version takes an +optional `options` object. If no `options` object is specified, it will +default with the above values. + #### `filehandle.write(string[, position[, encoding]])` -* `string` {string|Object} +* `string` {string} * `position` {integer} The offset from the beginning of the file where the data from `string` should be written. If `position` is not a `number` the data will be written at the current position. See the POSIX pwrite(2) @@ -654,13 +674,13 @@ changes: * `encoding` {string} The expected string encoding. **Default:** `'utf8'` * Returns: {Promise} -Write `string` to the file. If `string` is not a string, or an object with an -own `toString` function property, the promise is rejected with an error. +Write `string` to the file. If `string` is not a string, the promise is +rejected with an error. The promise is resolved with an object containing two properties: * `bytesWritten` {integer} the number of bytes written -* `buffer` {string|Object} a reference to the `string` written. +* `buffer` {string} a reference to the `string` written. It is unsafe to use `filehandle.write()` multiple times on the same file without waiting for the promise to be resolved (or rejected). For this @@ -4390,7 +4410,7 @@ changes: --> * `fd` {integer} -* `buffer` {Buffer|TypedArray|DataView|string|Object} +* `buffer` {Buffer|TypedArray|DataView} * `offset` {integer} * `length` {integer} * `position` {integer} @@ -4399,8 +4419,7 @@ changes: * `bytesWritten` {integer} * `buffer` {Buffer|TypedArray|DataView} -Write `buffer` to the file specified by `fd`. If `buffer` is a normal object, it -must have an own `toString` function property. +Write `buffer` to the file specified by `fd`. `offset` determines the part of the buffer to be written, and `length` is an integer specifying the number of bytes to write. @@ -5791,14 +5810,28 @@ changes: --> * `fd` {integer} -* `buffer` {Buffer|TypedArray|DataView|string|Object} +* `buffer` {Buffer|TypedArray|DataView} * `offset` {integer} * `length` {integer} * `position` {integer} * Returns: {number} The number of bytes written. -If `buffer` is a plain object, it must have an own (not inherited) `toString` -function property. +For detailed information, see the documentation of the asynchronous version of +this API: [`fs.write(fd, buffer...)`][]. + +### `fs.writeSync(fd, buffer, options)` + + + +* `fd` {integer} +* `buffer` {Buffer|TypedArray|DataView} +* `options` {Object} + * `offset` {integer} **Default:** `0` + * `length` {integer} **Default:** `buffer.byteLength` + * `position` {integer} **Default:** `null` +* Returns: {number} The number of bytes written. For detailed information, see the documentation of the asynchronous version of this API: [`fs.write(fd, buffer...)`][]. @@ -5808,6 +5841,10 @@ this API: [`fs.write(fd, buffer...)`][]. * `fd` {integer} -* `string` {string|Object} +* `string` {string} * `position` {integer} * `encoding` {string} * Returns: {number} The number of bytes written. -If `string` is a plain object, it must have an own (not inherited) `toString` -function property. - For detailed information, see the documentation of the asynchronous version of this API: [`fs.write(fd, string...)`][]. diff --git a/lib/fs.js b/lib/fs.js index 2895c11d8ecf1d..a0608e32771077 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -116,6 +116,7 @@ const { validateRmOptionsSync, validateRmdirOptions, validateStringAfterArrayBufferView, + validatePrimitiveStringAfterArrayBufferView, warnOnNonPortableTemplate } = require('internal/fs/utils'); const { @@ -852,16 +853,27 @@ ObjectDefineProperty(write, internalUtil.customPromisifyArgs, * Synchronously writes `buffer` to the * specified `fd` (file descriptor). * @param {number} fd - * @param {Buffer | TypedArray | DataView | string | object} buffer - * @param {number} [offset] - * @param {number} [length] - * @param {number} [position] + * @param {Buffer | TypedArray | DataView | string} buffer + * @param {{ + * offset?: number; + * length?: number; + * position?: number; + * }} [offset] * @returns {number} */ function writeSync(fd, buffer, offset, length, position) { fd = getValidatedFd(fd); const ctx = {}; let result; + + if (typeof offset === 'object' && offset !== null) { + ({ + offset = 0, + length = buffer.byteLength, + position = null + } = offset); + } + if (isArrayBufferView(buffer)) { if (position === undefined) position = null; @@ -876,7 +888,7 @@ function writeSync(fd, buffer, offset, length, position) { result = binding.writeBuffer(fd, buffer, offset, length, position, undefined, ctx); } else { - validateStringAfterArrayBufferView(buffer, 'buffer'); + validatePrimitiveStringAfterArrayBufferView(buffer, 'buffer'); validateEncoding(buffer, length); if (offset === undefined) diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index 868c7df2f1ed79..50c69f3cf8b836 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -66,6 +66,7 @@ const { validateRmOptions, validateRmdirOptions, validateStringAfterArrayBufferView, + validatePrimitiveStringAfterArrayBufferView, warnOnNonPortableTemplate, } = require('internal/fs/utils'); const { opendir } = require('internal/fs/dir'); @@ -560,7 +561,18 @@ async function readv(handle, buffers, position) { return { bytesRead, buffers }; } -async function write(handle, buffer, offset, length, position) { +async function write(handle, bufferOrOptions, offset, length, position) { + let buffer = bufferOrOptions; + if (!isArrayBufferView(buffer) && typeof buffer !== 'string') { + validateBuffer(bufferOrOptions?.buffer); + ({ + buffer, + offset = 0, + length = buffer.byteLength, + position = null + } = bufferOrOptions); + } + if (buffer?.byteLength === 0) return { bytesWritten: 0, buffer }; @@ -581,7 +593,7 @@ async function write(handle, buffer, offset, length, position) { return { bytesWritten, buffer }; } - validateStringAfterArrayBufferView(buffer, 'buffer'); + validatePrimitiveStringAfterArrayBufferView(buffer, 'buffer'); validateEncoding(buffer, length); const bytesWritten = (await binding.writeString(handle.fd, buffer, offset, length, kUsePromises)) || 0; diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js index 481b5292b1d726..61670011372bb7 100644 --- a/lib/internal/fs/utils.js +++ b/lib/internal/fs/utils.js @@ -889,6 +889,16 @@ const validateStringAfterArrayBufferView = hideStackFrames((buffer, name) => { ); }); +const validatePrimitiveStringAfterArrayBufferView = hideStackFrames((buffer, name) => { + if (typeof buffer !== 'string') { + throw new ERR_INVALID_ARG_TYPE( + name, + ['string', 'Buffer', 'TypedArray', 'DataView'], + buffer + ); + } +}); + const validatePosition = hideStackFrames((position, name) => { if (typeof position === 'number') { validateInteger(position, 'position'); @@ -943,5 +953,6 @@ module.exports = { validateRmOptionsSync, validateRmdirOptions, validateStringAfterArrayBufferView, + validatePrimitiveStringAfterArrayBufferView, warnOnNonPortableTemplate }; From fe6a9eabf79f30813c48a2f8540c18bb07021ab2 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Tue, 25 Jan 2022 02:01:02 +0800 Subject: [PATCH 02/14] squash: add tests for filehandle.write and fs.writeSync --- .../test-fs-promises-write-optional-params.js | 87 +++++++++++++++++++ .../test-fs-write-sync-optional-params.js | 82 +++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 test/parallel/test-fs-promises-write-optional-params.js create mode 100644 test/parallel/test-fs-write-sync-optional-params.js diff --git a/test/parallel/test-fs-promises-write-optional-params.js b/test/parallel/test-fs-promises-write-optional-params.js new file mode 100644 index 00000000000000..3c099029a15082 --- /dev/null +++ b/test/parallel/test-fs-promises-write-optional-params.js @@ -0,0 +1,87 @@ +'use strict'; + +const common = require('../common'); + +// This test ensures that filehandle.write accepts "named parameters" object +// and doesn't interpret objects as strings + +const assert = require('assert'); +const fsPromises = require('fs').promises; +const path = require('path'); +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const dest = path.resolve(tmpdir.path, 'tmp.txt'); +const buffer = Buffer.from('zyx'); + +async function testInvalid(dest, expectedCode, params) { + let fh; + try { + fh = await fsPromises.open(dest, 'w+'); + await assert.rejects( + async () => fh.write(params), + { code: expectedCode }); + } finally { + await fh?.close(); + } +} + +async function testValid(dest, params) { + let fh; + try { + fh = await fsPromises.open(dest, 'w+'); + const writeResult = await fh.write(params); + const writeBufCopy = Uint8Array.prototype.slice.call(writeResult.buffer); + const readResult = await fh.read(params); + const readBufCopy = Uint8Array.prototype.slice.call(readResult.buffer); + + assert.ok(writeResult.bytesWritten >= readResult.bytesRead); + if (params.length !== undefined && params.length !== null) { + assert.strictEqual(writeResult.bytesWritten, params.length); + } + if (params.offset === undefined || params.offset === 0) { + assert.deepStrictEqual(writeBufCopy, readBufCopy); + } + assert.deepStrictEqual(writeResult.buffer, readResult.buffer); + } finally { + await fh?.close(); + } +} + +(async () => { + // Test if first argument is not wrongly interpreted as ArrayBufferView|string + for (const badParams of [ + undefined, null, true, 42, 42n, Symbol('42'), NaN, [], + {}, + { buffer: 'amNotParam' }, + { string: 'amNotParam' }, + { buffer: new Uint8Array(1).buffer }, + new Date(), + new String('notPrimitive'), + { toString() { return 'amObject'; } }, + { [Symbol.toPrimitive]: (hint) => 'amObject' }, + ]) { + await testInvalid(dest, 'ERR_INVALID_ARG_TYPE', badParams); + } + + // Various invalid params + await testInvalid(dest, 'ERR_OUT_OF_RANGE', { buffer, length: 5 }); + await testInvalid(dest, 'ERR_OUT_OF_RANGE', { buffer, offset: 5 }); + await testInvalid(dest, 'ERR_OUT_OF_RANGE', { buffer, offset: 1 }); + await testInvalid(dest, 'ERR_OUT_OF_RANGE', { buffer, length: 1, offset: 3 }); + await testInvalid(dest, 'ERR_OUT_OF_RANGE', { buffer, length: -1 }); + await testInvalid(dest, 'ERR_OUT_OF_RANGE', { buffer, offset: -1 }); + + // Test compatibility with filehandle.read counterpart with reused params + for (const params of [ + { buffer }, + { buffer, length: 1 }, + { buffer, position: 5 }, + { buffer, length: 1, position: 5 }, + { buffer, length: 1, position: -1, offset: 2 }, + { buffer, length: null }, + ]) { + await testValid(dest, params); + } +})().then(common.mustCall()); diff --git a/test/parallel/test-fs-write-sync-optional-params.js b/test/parallel/test-fs-write-sync-optional-params.js new file mode 100644 index 00000000000000..fc5e46c53bd51a --- /dev/null +++ b/test/parallel/test-fs-write-sync-optional-params.js @@ -0,0 +1,82 @@ +'use strict'; + +require('../common'); + +// This test ensures that fs.writeSync accepts "named parameters" object +// and doesn't interpret objects as strings + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const dest = path.resolve(tmpdir.path, 'tmp.txt'); +const buffer = Buffer.from('zyx'); + +function testInvalid(dest, expectedCode, ...bufferAndParams) { + let fd; + try { + fd = fs.openSync(dest, 'w+'); + assert.throws( + () => fs.writeSync(fd, ...bufferAndParams), + { code: expectedCode }); + } finally { + if (fd != null) fs.closeSync(fd); + } +} + +function testValid(dest, buffer, params) { + let fd; + try { + fd = fs.openSync(dest, 'w+'); + const bytesWritten = fs.writeSync(fd, buffer, params); + const bytesRead = fs.readSync(fd, buffer, params); + + assert.ok(bytesWritten >= bytesRead); + if (params.length !== undefined && params.length !== null) { + assert.strictEqual(bytesWritten, params.length); + } + } finally { + if (fd != null) fs.closeSync(fd); + } +} + +{ + // Test if second argument is not wrongly interpreted as string or params + for (const badBuffer of [ + undefined, null, true, 42, 42n, Symbol('42'), NaN, [], + {}, + { buffer: 'amNotParam' }, + { string: 'amNotParam' }, + { buffer: new Uint8Array(1) }, + { buffer: new Uint8Array(1).buffer }, + new Date(), + new String('notPrimitive'), + { toString() { return 'amObject'; } }, + { [Symbol.toPrimitive]: (hint) => 'amObject' }, + ]) { + testInvalid(dest, 'ERR_INVALID_ARG_TYPE', badBuffer); + } + + // Various invalid params + testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { length: 5 }); + testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { offset: 5 }); + testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { offset: 1 }); + testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { length: 1, offset: 3 }); + testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { length: -1 }); + testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { offset: -1 }); + + // Test compatibility with fs.readSync counterpart with reused params + for (const params of [ + {}, + { length: 1 }, + { position: 5 }, + { length: 1, position: 5 }, + { length: 1, position: -1, offset: 2 }, + { length: null }, + ]) { + testValid(dest, buffer, params); + } +} From e93fd9c8621ec97332d33899036f49d0416335df Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Tue, 25 Jan 2022 14:52:23 +0800 Subject: [PATCH 03/14] squash: add test cases for stringable objects --- test/parallel/test-fs-promises-file-handle-write.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-fs-promises-file-handle-write.js b/test/parallel/test-fs-promises-file-handle-write.js index 8d020522767853..7f3d12d4817042 100644 --- a/test/parallel/test-fs-promises-file-handle-write.js +++ b/test/parallel/test-fs-promises-file-handle-write.js @@ -53,7 +53,12 @@ async function validateNonUint8ArrayWrite() { async function validateNonStringValuesWrite() { const filePathForHandle = path.resolve(tmpDir, 'tmp-non-string-write.txt'); const fileHandle = await open(filePathForHandle, 'w+'); - const nonStringValues = [123, {}, new Map(), null, undefined, 0n, () => {}, Symbol(), true]; + const nonStringValues = [ + 123, {}, new Map(), null, undefined, 0n, () => {}, Symbol(), true, + new String('notPrimitive'), + { toString() { return 'amObject'; } }, + { [Symbol.toPrimitive]: (hint) => 'amObject' }, + ]; for (const nonStringValue of nonStringValues) { await assert.rejects( fileHandle.write(nonStringValue), From 1358d5fb2d64338cc63a5b87b94d3c690033a888 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Tue, 25 Jan 2022 18:16:02 +0800 Subject: [PATCH 04/14] squash: add test cases for fs.write --- test/parallel/test-fs-write.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-fs-write.js b/test/parallel/test-fs-write.js index 30e25b15808dd2..c30eba28bd95a0 100644 --- a/test/parallel/test-fs-write.js +++ b/test/parallel/test-fs-write.js @@ -155,7 +155,11 @@ fs.open(fn4, 'w', 0o644, common.mustSucceed((fd) => { ); }); -[false, 5, {}, [], null, undefined].forEach((data) => { +[ + false, 5, {}, [], null, undefined, + new String('notPrimitive'), + { [Symbol.toPrimitive]: (hint) => 'amObject' }, +].forEach((data) => { assert.throws( () => fs.write(1, data, common.mustNotCall()), { From b3d04aa895ec25a5662f8fe9dbbfe1e381b425d0 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Mon, 28 Feb 2022 03:06:22 +0800 Subject: [PATCH 05/14] squash: adjust default length value --- doc/api/fs.md | 6 +++--- lib/fs.js | 2 +- lib/internal/fs/promises.js | 2 +- test/parallel/test-fs-promises-write-optional-params.js | 2 +- test/parallel/test-fs-write-sync-optional-params.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index a1a7ff72d1a5f5..976397eb0a1723 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -605,7 +605,7 @@ changes: * `offset` {integer} The start position from within `buffer` where the data to write begins. **Default:** `0` * `length` {integer} The number of bytes from `buffer` to write. **Default:** - `buffer.byteLength` + `buffer.byteLength - offset` * `position` {integer} The offset from the beginning of the file where the data from `buffer` should be written. If `position` is not a `number`, the data will be written at the current position. See the POSIX pwrite(2) @@ -637,7 +637,7 @@ added: REPLACEME * `buffer` {Buffer|TypedArray|DataView} * `options` {Object} * `offset` {integer} **Default:** `0` - * `length` {integer} **Default:** `buffer.byteLength` + * `length` {integer} **Default:** `buffer.byteLength - offset` * `position` {integer} **Default:** `null` * Returns: {Promise} @@ -5829,7 +5829,7 @@ added: REPLACEME * `buffer` {Buffer|TypedArray|DataView} * `options` {Object} * `offset` {integer} **Default:** `0` - * `length` {integer} **Default:** `buffer.byteLength` + * `length` {integer} **Default:** `buffer.byteLength - offset` * `position` {integer} **Default:** `null` * Returns: {number} The number of bytes written. diff --git a/lib/fs.js b/lib/fs.js index a0608e32771077..15ce2a1f4a63da 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -869,7 +869,7 @@ function writeSync(fd, buffer, offset, length, position) { if (typeof offset === 'object' && offset !== null) { ({ offset = 0, - length = buffer.byteLength, + length = buffer.byteLength - offset, position = null } = offset); } diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index 50c69f3cf8b836..d66b7f78366e32 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -568,7 +568,7 @@ async function write(handle, bufferOrOptions, offset, length, position) { ({ buffer, offset = 0, - length = buffer.byteLength, + length = buffer.byteLength - offset, position = null } = bufferOrOptions); } diff --git a/test/parallel/test-fs-promises-write-optional-params.js b/test/parallel/test-fs-promises-write-optional-params.js index 3c099029a15082..eee9487833bcbd 100644 --- a/test/parallel/test-fs-promises-write-optional-params.js +++ b/test/parallel/test-fs-promises-write-optional-params.js @@ -68,7 +68,6 @@ async function testValid(dest, params) { // Various invalid params await testInvalid(dest, 'ERR_OUT_OF_RANGE', { buffer, length: 5 }); await testInvalid(dest, 'ERR_OUT_OF_RANGE', { buffer, offset: 5 }); - await testInvalid(dest, 'ERR_OUT_OF_RANGE', { buffer, offset: 1 }); await testInvalid(dest, 'ERR_OUT_OF_RANGE', { buffer, length: 1, offset: 3 }); await testInvalid(dest, 'ERR_OUT_OF_RANGE', { buffer, length: -1 }); await testInvalid(dest, 'ERR_OUT_OF_RANGE', { buffer, offset: -1 }); @@ -81,6 +80,7 @@ async function testValid(dest, params) { { buffer, length: 1, position: 5 }, { buffer, length: 1, position: -1, offset: 2 }, { buffer, length: null }, + { buffer, offset: 1 }, ]) { await testValid(dest, params); } diff --git a/test/parallel/test-fs-write-sync-optional-params.js b/test/parallel/test-fs-write-sync-optional-params.js index fc5e46c53bd51a..95654f32815e2d 100644 --- a/test/parallel/test-fs-write-sync-optional-params.js +++ b/test/parallel/test-fs-write-sync-optional-params.js @@ -63,7 +63,6 @@ function testValid(dest, buffer, params) { // Various invalid params testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { length: 5 }); testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { offset: 5 }); - testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { offset: 1 }); testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { length: 1, offset: 3 }); testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { length: -1 }); testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { offset: -1 }); @@ -76,6 +75,7 @@ function testValid(dest, buffer, params) { { length: 1, position: 5 }, { length: 1, position: -1, offset: 2 }, { length: null }, + { offset: 1 }, ]) { testValid(dest, buffer, params); } From 720ef24b15692dc487039633dc7c1fa04b408161 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Tue, 1 Mar 2022 16:20:45 +0800 Subject: [PATCH 06/14] squash: harden string validation in `fh.writeFile` and `fh.appendFile` --- doc/api/fs.md | 12 +++++++----- lib/internal/fs/promises.js | 3 +-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index 976397eb0a1723..9334ebcc6f934b 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -695,6 +695,10 @@ the end of the file. -* `data` {string|Buffer|TypedArray|DataView|Object|AsyncIterable|Iterable - |Stream} +* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Stream} * `options` {Object|string} * `encoding` {string|null} The expected character encoding when `data` is a string. **Default:** `'utf8'` * Returns: {Promise} Asynchronously writes data to a file, replacing the file if it already exists. -`data` can be a string, a buffer, an {AsyncIterable} or {Iterable} object, or an -object with an own `toString` function -property. The promise is resolved with no arguments upon success. +`data` can be a string, a buffer, an {AsyncIterable} or {Iterable} object. +The promise is resolved with no arguments upon success. If `options` is a string, then it specifies the `encoding`. diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index d66b7f78366e32..c47f3e3095d67a 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -65,7 +65,6 @@ const { validateOffsetLengthWrite, validateRmOptions, validateRmdirOptions, - validateStringAfterArrayBufferView, validatePrimitiveStringAfterArrayBufferView, warnOnNonPortableTemplate, } = require('internal/fs/utils'); @@ -823,7 +822,7 @@ async function writeFile(path, data, options) { const flag = options.flag || 'w'; if (!isArrayBufferView(data) && !isCustomIterable(data)) { - validateStringAfterArrayBufferView(data, 'data'); + validatePrimitiveStringAfterArrayBufferView(data, 'data'); data = Buffer.from(data, options.encoding || 'utf8'); } From ac6f1df196f444fabd3978bc54a9376d64726228 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Mon, 4 Apr 2022 02:47:26 +0800 Subject: [PATCH 07/14] squash: remove unused optional chaining Refs: https://github.com/nodejs/node/pull/42518 --- lib/internal/fs/promises.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index c47f3e3095d67a..4db1c8e162acd1 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -572,7 +572,7 @@ async function write(handle, bufferOrOptions, offset, length, position) { } = bufferOrOptions); } - if (buffer?.byteLength === 0) + if (buffer.byteLength === 0) return { bytesWritten: 0, buffer }; if (isArrayBufferView(buffer)) { From da8086fc801cc5b150b3176065db37deebce28d3 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Mon, 4 Apr 2022 05:22:59 +0800 Subject: [PATCH 08/14] squash: add changelog entry for `filehandle.appendFile` --- doc/api/fs.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index 9334ebcc6f934b..6579533e1e16a8 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -180,6 +180,10 @@ longer be used. -* `data` {string|Buffer|TypedArray|DataView|Object|AsyncIterable|Iterable - |Stream} +* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Stream} * `options` {Object|string} * `encoding` {string|null} **Default:** `'utf8'` * Returns: {Promise} Fulfills with `undefined` upon success. From a283264a2ec6cca76fd3b9153e6dfb19dbe83357 Mon Sep 17 00:00:00 2001 From: LiviaMedeiros Date: Mon, 4 Apr 2022 06:14:31 +0800 Subject: [PATCH 09/14] squash: remove redundant changelog entries string-oriented changes do not affect buffer-oriented methods --- doc/api/fs.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index 6579533e1e16a8..4b6b3293ca72c6 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -594,10 +594,6 @@ then resolves the promise with no arguments upon success. - -* `buffer` {Buffer|TypedArray|DataView} -* `options` {Object} - * `offset` {integer} **Default:** `0` - * `length` {integer} **Default:** `buffer.byteLength - offset` - * `position` {integer} **Default:** `null` -* Returns: {Promise} - -Write `buffer` to the file. - -Similar to the above `filehandle.write` function, this version takes an -optional `options` object. If no `options` object is specified, it will -default with the above values. - #### `filehandle.write(string[, position[, encoding]])` - -* `fd` {integer} -* `buffer` {Buffer|TypedArray|DataView} -* `options` {Object} - * `offset` {integer} **Default:** `0` - * `length` {integer} **Default:** `buffer.byteLength - offset` - * `position` {integer} **Default:** `null` -* Returns: {number} The number of bytes written. - -For detailed information, see the documentation of the asynchronous version of -this API: [`fs.write(fd, buffer...)`][]. - ### `fs.writeSync(fd, string[, position[, encoding]])` * `file` {string|Buffer|URL|FileHandle} filename or `FileHandle` -* `data` {string|Buffer|TypedArray|DataView|Object|AsyncIterable|Iterable - |Stream} +* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Stream} * `options` {Object|string} * `encoding` {string|null} **Default:** `'utf8'` * `mode` {integer} **Default:** `0o666` @@ -1531,8 +1530,7 @@ changes: * Returns: {Promise} Fulfills with `undefined` upon success. Asynchronously writes data to a file, replacing the file if it already exists. -`data` can be a string, a {Buffer}, or, an object with an own (not inherited) -`toString` function property. +`data` can be a string, a buffer, an {AsyncIterable} or {Iterable} object. The `encoding` option is ignored if `data` is a buffer.