Skip to content

Commit e378a56

Browse files
committed
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 <jasnell@gmail.com> Reviewed-By: Nicu Micleușanu <micnic90@gmail.com> Reviewed-By: Rod Vagg <rod@vagg.org>
1 parent 7084b9c commit e378a56

File tree

2 files changed

+116
-8
lines changed

2 files changed

+116
-8
lines changed

lib/fs.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ function getOptions(options, defaultOptions) {
5656
return options;
5757
}
5858

59+
function copyObject(source, target) {
60+
target = arguments.length >= 2 ? target : {};
61+
for (const key in source)
62+
target[key] = source[key];
63+
return target;
64+
}
65+
5966
function rethrow() {
6067
// TODO(thefourtheye) Throw error instead of warning in major version > 7
6168
process.emitWarning(
@@ -1230,11 +1237,11 @@ fs.appendFile = function(path, data, options, callback) {
12301237
callback = maybeCallback(arguments[arguments.length - 1]);
12311238
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
12321239

1233-
if (!options.flag)
1234-
options = util._extend({ flag: 'a' }, options);
1240+
// Don't make changes directly on options object
1241+
options = copyObject(options);
12351242

12361243
// force append behavior when using a supplied file descriptor
1237-
if (isFd(path))
1244+
if (!options.flag || isFd(path))
12381245
options.flag = 'a';
12391246

12401247
fs.writeFile(path, data, options, callback);
@@ -1243,11 +1250,11 @@ fs.appendFile = function(path, data, options, callback) {
12431250
fs.appendFileSync = function(path, data, options) {
12441251
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
12451252

1246-
if (!options.flag)
1247-
options = util._extend({ flag: 'a' }, options);
1253+
// Don't make changes directly on options object
1254+
options = copyObject(options);
12481255

12491256
// force append behavior when using a supplied file descriptor
1250-
if (isFd(path))
1257+
if (!options.flag || isFd(path))
12511258
options.flag = 'a';
12521259

12531260
fs.writeFileSync(path, data, options);
@@ -1305,6 +1312,9 @@ fs.watch = function(filename, options, listener) {
13051312
}
13061313
options = getOptions(options, {});
13071314

1315+
// Don't make changes directly on options object
1316+
options = copyObject(options);
1317+
13081318
if (options.persistent === undefined) options.persistent = true;
13091319
if (options.recursive === undefined) options.recursive = false;
13101320

@@ -1705,7 +1715,7 @@ function ReadStream(path, options) {
17051715
return new ReadStream(path, options);
17061716

17071717
// a little bit bigger buffer and water marks by default
1708-
options = Object.create(getOptions(options, {}));
1718+
options = copyObject(getOptions(options, {}));
17091719
if (options.highWaterMark === undefined)
17101720
options.highWaterMark = 64 * 1024;
17111721

@@ -1871,7 +1881,7 @@ function WriteStream(path, options) {
18711881
if (!(this instanceof WriteStream))
18721882
return new WriteStream(path, options);
18731883

1874-
options = Object.create(getOptions(options, {}));
1884+
options = copyObject(getOptions(options, {}));
18751885

18761886
Writable.call(this, options);
18771887

Lines changed: 98 additions & 0 deletions
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)