Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs: fix write methods param validation and docs #41677

Merged
merged 14 commits into from Apr 4, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
105 changes: 66 additions & 39 deletions doc/api/fs.md
Expand Up @@ -180,6 +180,10 @@ longer be used.
<!-- YAML
added: v10.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/41677
description: The `data` parameter no longer accepts objects with an
own `toString` function property.
- version:
- v15.14.0
- v14.18.0
Expand All @@ -195,8 +199,7 @@ changes:
strings anymore.
-->

* `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.
Expand Down Expand Up @@ -591,21 +594,17 @@ then resolves the promise with no arguments upon success.
<!-- YAML
added: v10.0.0
changes:
- version: v14.12.0
pr-url: https://github.com/nodejs/node/pull/34993
description: The `buffer` parameter will stringify an object with an
explicit `toString` function.
- version: v14.0.0
pr-url: https://github.com/nodejs/node/pull/31030
description: The `buffer` parameter won't coerce unsupported input to
buffers anymore.
-->

* `buffer` {Buffer|TypedArray|DataView|string|Object}
* `buffer` {Buffer|TypedArray|DataView}
LiviaMedeiros marked this conversation as resolved.
Show resolved Hide resolved
* `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)
Expand All @@ -614,13 +613,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
Expand All @@ -631,11 +627,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)`

<!-- YAML
added: REPLACEME
-->

* `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]])`

<!-- YAML
added: v10.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/41677
description: The `string` parameter no longer accepts objects with an
own `toString` function property.
- version: v14.12.0
pr-url: https://github.com/nodejs/node/pull/34993
description: The `string` parameter will stringify an object with an
aduh95 marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -646,21 +665,21 @@ changes:
strings anymore.
-->

* `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)
documentation for more detail.
* `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
Expand All @@ -675,6 +694,10 @@ the end of the file.
<!-- YAML
added: v10.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/41677
description: The `data` parameter no longer accepts objects with an
own `toString` function property.
- version:
- v15.14.0
- v14.18.0
Expand All @@ -690,17 +713,15 @@ changes:
strings anymore.
-->

* `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`.

Expand Down Expand Up @@ -4361,10 +4382,6 @@ changes:
description: Passing an invalid callback to the `callback` argument
now throws `ERR_INVALID_ARG_TYPE` instead of
`ERR_INVALID_CALLBACK`.
- version: v14.12.0
pr-url: https://github.com/nodejs/node/pull/34993
description: The `buffer` parameter will stringify an object with an
explicit `toString` function.
aduh95 marked this conversation as resolved.
Show resolved Hide resolved
- version: v14.0.0
pr-url: https://github.com/nodejs/node/pull/31030
description: The `buffer` parameter won't coerce unsupported input to
Expand All @@ -4390,7 +4407,7 @@ changes:
-->

* `fd` {integer}
* `buffer` {Buffer|TypedArray|DataView|string|Object}
* `buffer` {Buffer|TypedArray|DataView}
aduh95 marked this conversation as resolved.
Show resolved Hide resolved
* `offset` {integer}
* `length` {integer}
* `position` {integer}
Expand All @@ -4399,8 +4416,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.
Expand Down Expand Up @@ -5770,10 +5786,6 @@ this API: [`fs.writeFile()`][].
<!-- YAML
added: v0.1.21
changes:
- version: v14.12.0
pr-url: https://github.com/nodejs/node/pull/34993
description: The `buffer` parameter will stringify an object with an
explicit `toString` function.
- version: v14.0.0
pr-url: https://github.com/nodejs/node/pull/31030
description: The `buffer` parameter won't coerce unsupported input to
Expand All @@ -5791,14 +5803,28 @@ changes:
-->

* `fd` {integer}
* `buffer` {Buffer|TypedArray|DataView|string|Object}
* `buffer` {Buffer|TypedArray|DataView}
aduh95 marked this conversation as resolved.
Show resolved Hide resolved
* `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)`

<!-- YAML
added: REPLACEME
-->

* `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...)`][].
Expand All @@ -5808,6 +5834,10 @@ this API: [`fs.write(fd, buffer...)`][].
<!-- YAML
added: v0.11.5
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/41677
description: The `string` parameter no longer accepts objects with an
own `toString` function property.
- version: v14.12.0
pr-url: https://github.com/nodejs/node/pull/34993
description: The `string` parameter will stringify an object with an
Expand All @@ -5822,14 +5852,11 @@ changes:
-->

* `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...)`][].

Expand Down
22 changes: 17 additions & 5 deletions lib/fs.js
Expand Up @@ -116,6 +116,7 @@ const {
validateRmOptionsSync,
validateRmdirOptions,
validateStringAfterArrayBufferView,
validatePrimitiveStringAfterArrayBufferView,
warnOnNonPortableTemplate
} = require('internal/fs/utils');
const {
Expand Down Expand Up @@ -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 - offset,
position = null
} = offset);
}

if (isArrayBufferView(buffer)) {
if (position === undefined)
position = null;
Expand All @@ -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)
Expand Down
21 changes: 16 additions & 5 deletions lib/internal/fs/promises.js
Expand Up @@ -65,7 +65,7 @@ const {
validateOffsetLengthWrite,
validateRmOptions,
validateRmdirOptions,
validateStringAfterArrayBufferView,
validatePrimitiveStringAfterArrayBufferView,
warnOnNonPortableTemplate,
} = require('internal/fs/utils');
const { opendir } = require('internal/fs/dir');
Expand Down Expand Up @@ -560,8 +560,19 @@ async function readv(handle, buffers, position) {
return { bytesRead, buffers };
}

async function write(handle, buffer, offset, length, position) {
if (buffer?.byteLength === 0)
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 - offset,
position = null
} = bufferOrOptions);
}

if (buffer.byteLength === 0)
return { bytesWritten: 0, buffer };

if (isArrayBufferView(buffer)) {
Expand All @@ -581,7 +592,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;
Expand Down Expand Up @@ -811,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');
}

Expand Down
11 changes: 11 additions & 0 deletions lib/internal/fs/utils.js
Expand Up @@ -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');
Expand Down Expand Up @@ -943,5 +953,6 @@ module.exports = {
validateRmOptionsSync,
validateRmdirOptions,
validateStringAfterArrayBufferView,
validatePrimitiveStringAfterArrayBufferView,
warnOnNonPortableTemplate
};
7 changes: 6 additions & 1 deletion test/parallel/test-fs-promises-file-handle-write.js
Expand Up @@ -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),
Expand Down