Skip to content

Commit fe9f5bc

Browse files
thefourtheyejasnell
authored andcommitted
fs: don't alter user provided options object
This patch makes a copy of the `options` object before the fs module functions alter it. PR-URL: #7831 Fixes: #7655 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Nicu Micleușanu <[email protected]> Reviewed-By: Rod Vagg <[email protected]>
1 parent 169f485 commit fe9f5bc

File tree

2 files changed

+116
-8
lines changed

2 files changed

+116
-8
lines changed

lib/fs.js

+18-8
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ function getOptions(options, defaultOptions) {
6464
return options;
6565
}
6666

67+
function copyObject(source, target) {
68+
target = arguments.length >= 2 ? target : {};
69+
for (const key in source)
70+
target[key] = source[key];
71+
return target;
72+
}
73+
6774
function rethrow() {
6875
// TODO(thefourtheye) Throw error instead of warning in major version > 7
6976
process.emitWarning(
@@ -1280,11 +1287,11 @@ fs.appendFile = function(path, data, options, callback) {
12801287
callback = maybeCallback(arguments[arguments.length - 1]);
12811288
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
12821289

1283-
if (!options.flag)
1284-
options = util._extend({ flag: 'a' }, options);
1290+
// Don't make changes directly on options object
1291+
options = copyObject(options);
12851292

12861293
// force append behavior when using a supplied file descriptor
1287-
if (isFd(path))
1294+
if (!options.flag || isFd(path))
12881295
options.flag = 'a';
12891296

12901297
fs.writeFile(path, data, options, callback);
@@ -1293,11 +1300,11 @@ fs.appendFile = function(path, data, options, callback) {
12931300
fs.appendFileSync = function(path, data, options) {
12941301
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
12951302

1296-
if (!options.flag)
1297-
options = util._extend({ flag: 'a' }, options);
1303+
// Don't make changes directly on options object
1304+
options = copyObject(options);
12981305

12991306
// force append behavior when using a supplied file descriptor
1300-
if (isFd(path))
1307+
if (!options.flag || isFd(path))
13011308
options.flag = 'a';
13021309

13031310
fs.writeFileSync(path, data, options);
@@ -1355,6 +1362,9 @@ fs.watch = function(filename, options, listener) {
13551362
}
13561363
options = getOptions(options, {});
13571364

1365+
// Don't make changes directly on options object
1366+
options = copyObject(options);
1367+
13581368
if (options.persistent === undefined) options.persistent = true;
13591369
if (options.recursive === undefined) options.recursive = false;
13601370

@@ -1755,7 +1765,7 @@ function ReadStream(path, options) {
17551765
return new ReadStream(path, options);
17561766

17571767
// a little bit bigger buffer and water marks by default
1758-
options = Object.create(getOptions(options, {}));
1768+
options = copyObject(getOptions(options, {}));
17591769
if (options.highWaterMark === undefined)
17601770
options.highWaterMark = 64 * 1024;
17611771

@@ -1921,7 +1931,7 @@ function WriteStream(path, options) {
19211931
if (!(this instanceof WriteStream))
19221932
return new WriteStream(path, options);
19231933

1924-
options = Object.create(getOptions(options, {}));
1934+
options = copyObject(getOptions(options, {}));
19251935

19261936
Writable.call(this, options);
19271937

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
'use strict';
2+
3+
/*
4+
* These tests make sure that the `options` object passed to these functions are
5+
* never altered.
6+
*
7+
* Refer: https://github.com/nodejs/node/issues/7655
8+
*/
9+
10+
const common = require('../common');
11+
const assert = require('assert');
12+
const path = require('path');
13+
const fs = require('fs');
14+
const errHandler = (e) => assert.ifError(e);
15+
const options = Object.freeze({});
16+
common.refreshTmpDir();
17+
18+
{
19+
assert.doesNotThrow(() =>
20+
fs.readFile(__filename, options, common.mustCall(errHandler))
21+
);
22+
assert.doesNotThrow(() => fs.readFileSync(__filename, options));
23+
}
24+
25+
{
26+
assert.doesNotThrow(() =>
27+
fs.readdir(__dirname, options, common.mustCall(errHandler))
28+
);
29+
assert.doesNotThrow(() => fs.readdirSync(__dirname, options));
30+
}
31+
32+
{
33+
const sourceFile = path.resolve(common.tmpDir, 'test-readlink');
34+
const linkFile = path.resolve(common.tmpDir, 'test-readlink-link');
35+
36+
fs.writeFileSync(sourceFile, '');
37+
fs.symlinkSync(sourceFile, linkFile);
38+
39+
assert.doesNotThrow(() =>
40+
fs.readlink(linkFile, options, common.mustCall(errHandler))
41+
);
42+
assert.doesNotThrow(() => fs.readlinkSync(linkFile, options));
43+
}
44+
45+
{
46+
const fileName = path.resolve(common.tmpDir, 'writeFile');
47+
assert.doesNotThrow(() => fs.writeFileSync(fileName, 'ABCD', options));
48+
assert.doesNotThrow(() =>
49+
fs.writeFile(fileName, 'ABCD', options, common.mustCall(errHandler))
50+
);
51+
}
52+
53+
{
54+
const fileName = path.resolve(common.tmpDir, 'appendFile');
55+
assert.doesNotThrow(() => fs.appendFileSync(fileName, 'ABCD', options));
56+
assert.doesNotThrow(() =>
57+
fs.appendFile(fileName, 'ABCD', options, common.mustCall(errHandler))
58+
);
59+
}
60+
61+
if (!common.isAix) {
62+
// TODO(thefourtheye) Remove this guard once
63+
// https://github.com/nodejs/node/issues/5085 is fixed
64+
{
65+
let watch;
66+
assert.doesNotThrow(() => watch = fs.watch(__filename, options, () => {}));
67+
watch.close();
68+
}
69+
70+
{
71+
assert.doesNotThrow(() => fs.watchFile(__filename, options, () => {}));
72+
fs.unwatchFile(__filename);
73+
}
74+
}
75+
76+
{
77+
assert.doesNotThrow(() => fs.realpathSync(__filename, options));
78+
assert.doesNotThrow(() =>
79+
fs.realpath(__filename, options, common.mustCall(errHandler))
80+
);
81+
}
82+
83+
{
84+
const tempFileName = path.resolve(common.tmpDir, 'mkdtemp-');
85+
assert.doesNotThrow(() => fs.mkdtempSync(tempFileName, options));
86+
assert.doesNotThrow(() =>
87+
fs.mkdtemp(tempFileName, options, common.mustCall(errHandler))
88+
);
89+
}
90+
91+
{
92+
const fileName = path.resolve(common.tmpDir, 'streams');
93+
assert.doesNotThrow(() => {
94+
fs.WriteStream(fileName, options).once('open', () => {
95+
assert.doesNotThrow(() => fs.ReadStream(fileName, options));
96+
});
97+
});
98+
}

0 commit comments

Comments
 (0)