Skip to content

Commit beb75bd

Browse files
ljharbruyadorno
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 76e24f9 commit beb75bd

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
@@ -4143,6 +4143,10 @@ This happens when:
41434143
<!-- YAML
41444144
added: v0.0.2
41454145
changes:
4146+
- version: REPLACEME
4147+
pr-url: https://github.com/nodejs/node/pull/34993
4148+
description: The `buffer` parameter will stringify an object with an
4149+
explicit `toString` function.
41464150
- version: v14.0.0
41474151
pr-url: https://github.com/nodejs/node/pull/31030
41484152
description: The `buffer` parameter won't coerce unsupported input to
@@ -4168,7 +4172,7 @@ changes:
41684172
-->
41694173

41704174
* `fd` {integer}
4171-
* `buffer` {Buffer|TypedArray|DataView}
4175+
* `buffer` {Buffer|TypedArray|DataView|string|Object}
41724176
* `offset` {integer}
41734177
* `length` {integer}
41744178
* `position` {integer}
@@ -4177,7 +4181,8 @@ changes:
41774181
* `bytesWritten` {integer}
41784182
* `buffer` {Buffer|TypedArray|DataView}
41794183

4180-
Write `buffer` to the file specified by `fd`.
4184+
Write `buffer` to the file specified by `fd`. If `buffer` is a normal object, it
4185+
must have an own `toString` function property.
41814186

41824187
`offset` determines the part of the buffer to be written, and `length` is
41834188
an integer specifying the number of bytes to write.
@@ -4204,6 +4209,10 @@ the end of the file.
42044209
<!-- YAML
42054210
added: v0.11.5
42064211
changes:
4212+
- version: REPLACEME
4213+
pr-url: https://github.com/nodejs/node/pull/34993
4214+
description: The `string` parameter will stringify an object with an
4215+
explicit `toString` function.
42074216
- version: v14.0.0
42084217
pr-url: https://github.com/nodejs/node/pull/31030
42094218
description: The `string` parameter won't coerce unsupported input to
@@ -4222,16 +4231,16 @@ changes:
42224231
-->
42234232

42244233
* `fd` {integer}
4225-
* `string` {string}
4234+
* `string` {string|Object}
42264235
* `position` {integer}
42274236
* `encoding` {string} **Default:** `'utf8'`
42284237
* `callback` {Function}
42294238
* `err` {Error}
42304239
* `written` {integer}
42314240
* `string` {string}
42324241

4233-
Write `string` to the file specified by `fd`. If `string` is not a string, then
4234-
an exception will be thrown.
4242+
Write `string` to the file specified by `fd`. If `string` is not a string, or an
4243+
object with an own `toString` function property, then an exception is thrown.
42354244

42364245
`position` refers to the offset from the beginning of the file where this data
42374246
should be written. If `typeof position !== 'number'` the data will be written at
@@ -4263,6 +4272,10 @@ details.
42634272
<!-- YAML
42644273
added: v0.1.29
42654274
changes:
4275+
- version: REPLACEME
4276+
pr-url: https://github.com/nodejs/node/pull/34993
4277+
description: The `data` parameter will stringify an object with an
4278+
explicit `toString` function.
42664279
- version: v14.0.0
42674280
pr-url: https://github.com/nodejs/node/pull/31030
42684281
description: The `data` parameter won't coerce unsupported input to
@@ -4288,7 +4301,7 @@ changes:
42884301
-->
42894302

42904303
* `file` {string|Buffer|URL|integer} filename or file descriptor
4291-
* `data` {string|Buffer|TypedArray|DataView}
4304+
* `data` {string|Buffer|TypedArray|DataView|Object}
42924305
* `options` {Object|string}
42934306
* `encoding` {string|null} **Default:** `'utf8'`
42944307
* `mode` {integer} **Default:** `0o666`
@@ -4304,6 +4317,7 @@ When `file` is a file descriptor, the behavior is similar to calling
43044317
a file descriptor.
43054318

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

43084322
```js
43094323
const data = new Uint8Array(Buffer.from('Hello Node.js'));
@@ -4353,6 +4367,10 @@ to contain only `', World'`.
43534367
<!-- YAML
43544368
added: v0.1.29
43554369
changes:
4370+
- version: REPLACEME
4371+
pr-url: https://github.com/nodejs/node/pull/34993
4372+
description: The `data` parameter will stringify an object with an
4373+
explicit `toString` function.
43564374
- version: v14.0.0
43574375
pr-url: https://github.com/nodejs/node/pull/31030
43584376
description: The `data` parameter won't coerce unsupported input to
@@ -4370,7 +4388,7 @@ changes:
43704388
-->
43714389

43724390
* `file` {string|Buffer|URL|integer} filename or file descriptor
4373-
* `data` {string|Buffer|TypedArray|DataView}
4391+
* `data` {string|Buffer|TypedArray|DataView|Object}
43744392
* `options` {Object|string}
43754393
* `encoding` {string|null} **Default:** `'utf8'`
43764394
* `mode` {integer} **Default:** `0o666`
@@ -4385,6 +4403,10 @@ this API: [`fs.writeFile()`][].
43854403
<!-- YAML
43864404
added: v0.1.21
43874405
changes:
4406+
- version: REPLACEME
4407+
pr-url: https://github.com/nodejs/node/pull/34993
4408+
description: The `buffer` parameter will stringify an object with an
4409+
explicit `toString` function.
43884410
- version: v14.0.0
43894411
pr-url: https://github.com/nodejs/node/pull/31030
43904412
description: The `buffer` parameter won't coerce unsupported input to
@@ -4402,7 +4424,7 @@ changes:
44024424
-->
44034425

44044426
* `fd` {integer}
4405-
* `buffer` {Buffer|TypedArray|DataView}
4427+
* `buffer` {Buffer|TypedArray|DataView|string|Object}
44064428
* `offset` {integer}
44074429
* `length` {integer}
44084430
* `position` {integer}
@@ -4415,6 +4437,10 @@ this API: [`fs.write(fd, buffer...)`][].
44154437
<!-- YAML
44164438
added: v0.11.5
44174439
changes:
4440+
- version: REPLACEME
4441+
pr-url: https://github.com/nodejs/node/pull/34993
4442+
description: The `string` parameter will stringify an object with an
4443+
explicit `toString` function.
44184444
- version: v14.0.0
44194445
pr-url: https://github.com/nodejs/node/pull/31030
44204446
description: The `string` parameter won't coerce unsupported input to
@@ -4425,7 +4451,7 @@ changes:
44254451
-->
44264452

44274453
* `fd` {integer}
4428-
* `string` {string}
4454+
* `string` {string|Object}
44294455
* `position` {integer}
44304456
* `encoding` {string}
44314457
* Returns: {number} The number of bytes written.
@@ -4788,13 +4814,17 @@ This function does not work on AIX versions before 7.1, it will resolve the
47884814
<!-- YAML
47894815
added: v10.0.0
47904816
changes:
4817+
- version: REPLACEME
4818+
pr-url: https://github.com/nodejs/node/pull/34993
4819+
description: The `buffer` parameter will stringify an object with an
4820+
explicit `toString` function.
47914821
- version: v14.0.0
47924822
pr-url: https://github.com/nodejs/node/pull/31030
47934823
description: The `buffer` parameter won't coerce unsupported input to
47944824
buffers anymore.
47954825
-->
47964826

4797-
* `buffer` {Buffer|Uint8Array}
4827+
* `buffer` {Buffer|Uint8Array|string|Object}
47984828
* `offset` {integer}
47994829
* `length` {integer}
48004830
* `position` {integer}
@@ -4825,19 +4855,23 @@ the end of the file.
48254855
<!-- YAML
48264856
added: v10.0.0
48274857
changes:
4858+
- version: REPLACEME
4859+
pr-url: https://github.com/nodejs/node/pull/34993
4860+
description: The `string` parameter will stringify an object with an
4861+
explicit `toString` function.
48284862
- version: v14.0.0
48294863
pr-url: https://github.com/nodejs/node/pull/31030
48304864
description: The `string` parameter won't coerce unsupported input to
48314865
strings anymore.
48324866
-->
48334867

4834-
* `string` {string}
4868+
* `string` {string|Object}
48354869
* `position` {integer}
48364870
* `encoding` {string} **Default:** `'utf8'`
48374871
* Returns: {Promise}
48384872

4839-
Write `string` to the file. If `string` is not a string, then
4840-
an exception will be thrown.
4873+
Write `string` to the file. If `string` is not a string, or an
4874+
object with an own `toString` function property, then an exception is thrown.
48414875

48424876
The `Promise` is resolved with an object containing a `bytesWritten` property
48434877
identifying the number of bytes written, and a `buffer` property containing
@@ -4861,20 +4895,24 @@ the end of the file.
48614895
<!-- YAML
48624896
added: v10.0.0
48634897
changes:
4898+
- version: REPLACEME
4899+
pr-url: https://github.com/nodejs/node/pull/34993
4900+
description: The `data` parameter will stringify an object with an
4901+
explicit `toString` function.
48644902
- version: v14.0.0
48654903
pr-url: https://github.com/nodejs/node/pull/31030
48664904
description: The `data` parameter won't coerce unsupported input to
48674905
strings anymore.
48684906
-->
48694907

4870-
* `data` {string|Buffer|Uint8Array}
4908+
* `data` {string|Buffer|Uint8Array|Object}
48714909
* `options` {Object|string}
48724910
* `encoding` {string|null} **Default:** `'utf8'`
48734911
* Returns: {Promise}
48744912

48754913
Asynchronously writes data to a file, replacing the file if it already exists.
4876-
`data` can be a string or a buffer. The `Promise` will be resolved with no
4877-
arguments upon success.
4914+
`data` can be a string, a buffer, or an object with an own `toString` function
4915+
property. The `Promise` is resolved with no arguments upon success.
48784916

48794917
The `encoding` option is ignored if `data` is a buffer.
48804918

@@ -5492,23 +5530,27 @@ The `atime` and `mtime` arguments follow these rules:
54925530
<!-- YAML
54935531
added: v10.0.0
54945532
changes:
5533+
- version: REPLACEME
5534+
pr-url: https://github.com/nodejs/node/pull/34993
5535+
description: The `data` parameter will stringify an object with an
5536+
explicit `toString` function.
54955537
- version: v14.0.0
54965538
pr-url: https://github.com/nodejs/node/pull/31030
54975539
description: The `data` parameter won't coerce unsupported input to
54985540
strings anymore.
54995541
-->
55005542

55015543
* `file` {string|Buffer|URL|FileHandle} filename or `FileHandle`
5502-
* `data` {string|Buffer|Uint8Array}
5544+
* `data` {string|Buffer|Uint8Array|Object}
55035545
* `options` {Object|string}
55045546
* `encoding` {string|null} **Default:** `'utf8'`
55055547
* `mode` {integer} **Default:** `0o666`
55065548
* `flag` {string} See [support of file system `flags`][]. **Default:** `'w'`.
55075549
* Returns: {Promise}
55085550

55095551
Asynchronously writes data to a file, replacing the file if it already exists.
5510-
`data` can be a string or a buffer. The `Promise` will be resolved with no
5511-
arguments upon success.
5552+
`data` can be a string, a buffer, or an object with an own `toString` function
5553+
property. The `Promise` is resolved with no arguments upon success.
55125554

55135555
The `encoding` option is ignored if `data` is a buffer.
55145556

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)