Skip to content

Commit 9cf9e4a

Browse files
ljharbTrott
authored andcommitted
fs: loosen validation to allow objects with an own toString function
PR-URL: #34993 Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Bradley Farias <bradley.meck@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Derek Lewis <DerekNonGeneric@inf.is>
1 parent 0577127 commit 9cf9e4a

File tree

5 files changed

+113
-28
lines changed

5 files changed

+113
-28
lines changed

doc/api/fs.md

+61-19
Original file line numberDiff line numberDiff line change
@@ -4163,6 +4163,10 @@ This happens when:
41634163
<!-- YAML
41644164
added: v0.0.2
41654165
changes:
4166+
- version: REPLACEME
4167+
pr-url: https://github.com/nodejs/node/pull/34993
4168+
description: The `buffer` parameter will stringify an object with an
4169+
explicit `toString` function.
41664170
- version: v14.0.0
41674171
pr-url: https://github.com/nodejs/node/pull/31030
41684172
description: The `buffer` parameter won't coerce unsupported input to
@@ -4188,7 +4192,7 @@ changes:
41884192
-->
41894193

41904194
* `fd` {integer}
4191-
* `buffer` {Buffer|TypedArray|DataView}
4195+
* `buffer` {Buffer|TypedArray|DataView|string|Object}
41924196
* `offset` {integer}
41934197
* `length` {integer}
41944198
* `position` {integer}
@@ -4197,7 +4201,8 @@ changes:
41974201
* `bytesWritten` {integer}
41984202
* `buffer` {Buffer|TypedArray|DataView}
41994203

4200-
Write `buffer` to the file specified by `fd`.
4204+
Write `buffer` to the file specified by `fd`. If `buffer` is a normal object, it
4205+
must have an own `toString` function property.
42014206

42024207
`offset` determines the part of the buffer to be written, and `length` is
42034208
an integer specifying the number of bytes to write.
@@ -4224,6 +4229,10 @@ the end of the file.
42244229
<!-- YAML
42254230
added: v0.11.5
42264231
changes:
4232+
- version: REPLACEME
4233+
pr-url: https://github.com/nodejs/node/pull/34993
4234+
description: The `string` parameter will stringify an object with an
4235+
explicit `toString` function.
42274236
- version: v14.0.0
42284237
pr-url: https://github.com/nodejs/node/pull/31030
42294238
description: The `string` parameter won't coerce unsupported input to
@@ -4242,16 +4251,16 @@ changes:
42424251
-->
42434252

42444253
* `fd` {integer}
4245-
* `string` {string}
4254+
* `string` {string|Object}
42464255
* `position` {integer}
42474256
* `encoding` {string} **Default:** `'utf8'`
42484257
* `callback` {Function}
42494258
* `err` {Error}
42504259
* `written` {integer}
42514260
* `string` {string}
42524261

4253-
Write `string` to the file specified by `fd`. If `string` is not a string, then
4254-
an exception will be thrown.
4262+
Write `string` to the file specified by `fd`. If `string` is not a string, or an
4263+
object with an own `toString` function property, then an exception is thrown.
42554264

42564265
`position` refers to the offset from the beginning of the file where this data
42574266
should be written. If `typeof position !== 'number'` the data will be written at
@@ -4283,6 +4292,10 @@ details.
42834292
<!-- YAML
42844293
added: v0.1.29
42854294
changes:
4295+
- version: REPLACEME
4296+
pr-url: https://github.com/nodejs/node/pull/34993
4297+
description: The `data` parameter will stringify an object with an
4298+
explicit `toString` function.
42864299
- version: v14.0.0
42874300
pr-url: https://github.com/nodejs/node/pull/31030
42884301
description: The `data` parameter won't coerce unsupported input to
@@ -4308,7 +4321,7 @@ changes:
43084321
-->
43094322

43104323
* `file` {string|Buffer|URL|integer} filename or file descriptor
4311-
* `data` {string|Buffer|TypedArray|DataView}
4324+
* `data` {string|Buffer|TypedArray|DataView|Object}
43124325
* `options` {Object|string}
43134326
* `encoding` {string|null} **Default:** `'utf8'`
43144327
* `mode` {integer} **Default:** `0o666`
@@ -4324,6 +4337,7 @@ When `file` is a file descriptor, the behavior is similar to calling
43244337
a file descriptor.
43254338

43264339
The `encoding` option is ignored if `data` is a buffer.
4340+
If `data` is a normal object, it must have an own `toString` function property.
43274341

43284342
```js
43294343
const data = new Uint8Array(Buffer.from('Hello Node.js'));
@@ -4373,6 +4387,10 @@ to contain only `', World'`.
43734387
<!-- YAML
43744388
added: v0.1.29
43754389
changes:
4390+
- version: REPLACEME
4391+
pr-url: https://github.com/nodejs/node/pull/34993
4392+
description: The `data` parameter will stringify an object with an
4393+
explicit `toString` function.
43764394
- version: v14.0.0
43774395
pr-url: https://github.com/nodejs/node/pull/31030
43784396
description: The `data` parameter won't coerce unsupported input to
@@ -4390,7 +4408,7 @@ changes:
43904408
-->
43914409

43924410
* `file` {string|Buffer|URL|integer} filename or file descriptor
4393-
* `data` {string|Buffer|TypedArray|DataView}
4411+
* `data` {string|Buffer|TypedArray|DataView|Object}
43944412
* `options` {Object|string}
43954413
* `encoding` {string|null} **Default:** `'utf8'`
43964414
* `mode` {integer} **Default:** `0o666`
@@ -4405,6 +4423,10 @@ this API: [`fs.writeFile()`][].
44054423
<!-- YAML
44064424
added: v0.1.21
44074425
changes:
4426+
- version: REPLACEME
4427+
pr-url: https://github.com/nodejs/node/pull/34993
4428+
description: The `buffer` parameter will stringify an object with an
4429+
explicit `toString` function.
44084430
- version: v14.0.0
44094431
pr-url: https://github.com/nodejs/node/pull/31030
44104432
description: The `buffer` parameter won't coerce unsupported input to
@@ -4422,7 +4444,7 @@ changes:
44224444
-->
44234445

44244446
* `fd` {integer}
4425-
* `buffer` {Buffer|TypedArray|DataView}
4447+
* `buffer` {Buffer|TypedArray|DataView|string|Object}
44264448
* `offset` {integer}
44274449
* `length` {integer}
44284450
* `position` {integer}
@@ -4435,6 +4457,10 @@ this API: [`fs.write(fd, buffer...)`][].
44354457
<!-- YAML
44364458
added: v0.11.5
44374459
changes:
4460+
- version: REPLACEME
4461+
pr-url: https://github.com/nodejs/node/pull/34993
4462+
description: The `string` parameter will stringify an object with an
4463+
explicit `toString` function.
44384464
- version: v14.0.0
44394465
pr-url: https://github.com/nodejs/node/pull/31030
44404466
description: The `string` parameter won't coerce unsupported input to
@@ -4445,7 +4471,7 @@ changes:
44454471
-->
44464472

44474473
* `fd` {integer}
4448-
* `string` {string}
4474+
* `string` {string|Object}
44494475
* `position` {integer}
44504476
* `encoding` {string}
44514477
* Returns: {number} The number of bytes written.
@@ -4812,13 +4838,17 @@ This function does not work on AIX versions before 7.1, it will resolve the
48124838
<!-- YAML
48134839
added: v10.0.0
48144840
changes:
4841+
- version: REPLACEME
4842+
pr-url: https://github.com/nodejs/node/pull/34993
4843+
description: The `buffer` parameter will stringify an object with an
4844+
explicit `toString` function.
48154845
- version: v14.0.0
48164846
pr-url: https://github.com/nodejs/node/pull/31030
48174847
description: The `buffer` parameter won't coerce unsupported input to
48184848
buffers anymore.
48194849
-->
48204850

4821-
* `buffer` {Buffer|Uint8Array}
4851+
* `buffer` {Buffer|Uint8Array|string|Object}
48224852
* `offset` {integer}
48234853
* `length` {integer}
48244854
* `position` {integer}
@@ -4849,19 +4879,23 @@ the end of the file.
48494879
<!-- YAML
48504880
added: v10.0.0
48514881
changes:
4882+
- version: REPLACEME
4883+
pr-url: https://github.com/nodejs/node/pull/34993
4884+
description: The `string` parameter will stringify an object with an
4885+
explicit `toString` function.
48524886
- version: v14.0.0
48534887
pr-url: https://github.com/nodejs/node/pull/31030
48544888
description: The `string` parameter won't coerce unsupported input to
48554889
strings anymore.
48564890
-->
48574891

4858-
* `string` {string}
4892+
* `string` {string|Object}
48594893
* `position` {integer}
48604894
* `encoding` {string} **Default:** `'utf8'`
48614895
* Returns: {Promise}
48624896

4863-
Write `string` to the file. If `string` is not a string, then
4864-
an exception will be thrown.
4897+
Write `string` to the file. If `string` is not a string, or an
4898+
object with an own `toString` function property, then an exception is thrown.
48654899

48664900
The `Promise` is resolved with an object containing a `bytesWritten` property
48674901
identifying the number of bytes written, and a `buffer` property containing
@@ -4885,20 +4919,24 @@ the end of the file.
48854919
<!-- YAML
48864920
added: v10.0.0
48874921
changes:
4922+
- version: REPLACEME
4923+
pr-url: https://github.com/nodejs/node/pull/34993
4924+
description: The `data` parameter will stringify an object with an
4925+
explicit `toString` function.
48884926
- version: v14.0.0
48894927
pr-url: https://github.com/nodejs/node/pull/31030
48904928
description: The `data` parameter won't coerce unsupported input to
48914929
strings anymore.
48924930
-->
48934931

4894-
* `data` {string|Buffer|Uint8Array}
4932+
* `data` {string|Buffer|Uint8Array|Object}
48954933
* `options` {Object|string}
48964934
* `encoding` {string|null} **Default:** `'utf8'`
48974935
* Returns: {Promise}
48984936

48994937
Asynchronously writes data to a file, replacing the file if it already exists.
4900-
`data` can be a string or a buffer. The `Promise` will be resolved with no
4901-
arguments upon success.
4938+
`data` can be a string, a buffer, or an object with an own `toString` function
4939+
property. The `Promise` is resolved with no arguments upon success.
49024940

49034941
The `encoding` option is ignored if `data` is a buffer.
49044942

@@ -5516,23 +5554,27 @@ The `atime` and `mtime` arguments follow these rules:
55165554
<!-- YAML
55175555
added: v10.0.0
55185556
changes:
5557+
- version: REPLACEME
5558+
pr-url: https://github.com/nodejs/node/pull/34993
5559+
description: The `data` parameter will stringify an object with an
5560+
explicit `toString` function.
55195561
- version: v14.0.0
55205562
pr-url: https://github.com/nodejs/node/pull/31030
55215563
description: The `data` parameter won't coerce unsupported input to
55225564
strings anymore.
55235565
-->
55245566

55255567
* `file` {string|Buffer|URL|FileHandle} filename or `FileHandle`
5526-
* `data` {string|Buffer|Uint8Array}
5568+
* `data` {string|Buffer|Uint8Array|Object}
55275569
* `options` {Object|string}
55285570
* `encoding` {string|null} **Default:** `'utf8'`
55295571
* `mode` {integer} **Default:** `0o666`
55305572
* `flag` {string} See [support of file system `flags`][]. **Default:** `'w'`.
55315573
* Returns: {Promise}
55325574

55335575
Asynchronously writes data to a file, replacing the file if it already exists.
5534-
`data` can be a string or a buffer. The `Promise` will be resolved with no
5535-
arguments upon success.
5576+
`data` can be a string, a buffer, or an object with an own `toString` function
5577+
property. The `Promise` is resolved with no arguments upon success.
55365578

55375579
The `encoding` option is ignored if `data` is a buffer.
55385580

lib/fs.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const {
3737
ObjectDefineProperties,
3838
ObjectDefineProperty,
3939
Promise,
40+
String,
4041
} = primordials;
4142

4243
const { fs: constants } = internalBinding('constants');
@@ -663,7 +664,7 @@ function write(fd, buffer, offset, length, position, callback) {
663664

664665
const req = new FSReqCallback();
665666
req.oncomplete = wrapper;
666-
return binding.writeString(fd, buffer, offset, length, req);
667+
return binding.writeString(fd, String(buffer), offset, length, req);
667668
}
668669

669670
ObjectDefineProperty(write, internalUtil.customPromisifyArgs,
@@ -1383,7 +1384,7 @@ function writeFile(path, data, options, callback) {
13831384

13841385
if (!isArrayBufferView(data)) {
13851386
validateStringAfterArrayBufferView(data, 'data');
1386-
data = Buffer.from(data, options.encoding || 'utf8');
1387+
data = Buffer.from(String(data), options.encoding || 'utf8');
13871388
}
13881389

13891390
if (isFd(path)) {
@@ -1407,7 +1408,7 @@ function writeFileSync(path, data, options) {
14071408

14081409
if (!isArrayBufferView(data)) {
14091410
validateStringAfterArrayBufferView(data, 'data');
1410-
data = Buffer.from(data, options.encoding || 'utf8');
1411+
data = Buffer.from(String(data), options.encoding || 'utf8');
14111412
}
14121413

14131414
const flag = options.flag || 'w';

lib/internal/fs/utils.js

+18-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const {
55
BigInt,
66
DateNow,
77
Error,
8+
ObjectPrototypeHasOwnProperty,
89
Number,
910
NumberIsFinite,
1011
MathMin,
@@ -697,13 +698,24 @@ const getValidMode = hideStackFrames((mode, type) => {
697698
});
698699

699700
const validateStringAfterArrayBufferView = hideStackFrames((buffer, name) => {
700-
if (typeof buffer !== 'string') {
701-
throw new ERR_INVALID_ARG_TYPE(
702-
name,
703-
['string', 'Buffer', 'TypedArray', 'DataView'],
704-
buffer
705-
);
701+
if (typeof buffer === 'string') {
702+
return;
703+
}
704+
705+
if (
706+
typeof buffer === 'object' &&
707+
buffer !== null &&
708+
typeof buffer.toString === 'function' &&
709+
ObjectPrototypeHasOwnProperty(buffer, 'toString')
710+
) {
711+
return;
706712
}
713+
714+
throw new ERR_INVALID_ARG_TYPE(
715+
name,
716+
['string', 'Buffer', 'TypedArray', 'DataView'],
717+
buffer
718+
);
707719
});
708720

709721
module.exports = {

test/parallel/test-fs-write-file-sync.js

+14
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,17 @@ tmpdir.refresh();
101101
const content = fs.readFileSync(file, { encoding: 'utf8' });
102102
assert.strictEqual(content, 'hello world!');
103103
}
104+
105+
// Test writeFileSync with an object with an own toString function
106+
{
107+
const file = path.join(tmpdir.path, 'testWriteFileSyncStringify.txt');
108+
const data = {
109+
toString() {
110+
return 'hello world!';
111+
}
112+
};
113+
114+
fs.writeFileSync(file, data, { encoding: 'utf8', flag: 'a' });
115+
const content = fs.readFileSync(file, { encoding: 'utf8' });
116+
assert.strictEqual(content, String(data));
117+
}

test/parallel/test-fs-write.js

+16
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ tmpdir.refresh();
3232
const fn = path.join(tmpdir.path, 'write.txt');
3333
const fn2 = path.join(tmpdir.path, 'write2.txt');
3434
const fn3 = path.join(tmpdir.path, 'write3.txt');
35+
const fn4 = path.join(tmpdir.path, 'write4.txt');
3536
const expected = 'ümlaut.';
3637
const constants = fs.constants;
3738

@@ -134,6 +135,21 @@ fs.open(fn3, 'w', 0o644, common.mustCall((err, fd) => {
134135
fs.write(fd, expected, done);
135136
}));
136137

138+
fs.open(fn4, 'w', 0o644, common.mustCall((err, fd) => {
139+
assert.ifError(err);
140+
141+
const done = common.mustCall((err, written) => {
142+
assert.ifError(err);
143+
assert.strictEqual(written, Buffer.byteLength(expected));
144+
fs.closeSync(fd);
145+
});
146+
147+
const data = {
148+
toString() { return expected; }
149+
};
150+
fs.write(fd, data, done);
151+
}));
152+
137153
[false, 'test', {}, [], null, undefined].forEach((i) => {
138154
assert.throws(
139155
() => fs.write(i, common.mustNotCall()),

0 commit comments

Comments
 (0)