Skip to content

Commit bcfb9bd

Browse files
committed
fs: adjust typecheck for type in fs.symlink()
Throws `TypeError` instead of `Error` Enables autodetection on Windows if `type === undefined` Explicitly disallows unknown strings and non-string values
1 parent 4556445 commit bcfb9bd

6 files changed

Lines changed: 45 additions & 36 deletions

File tree

doc/api/fs.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1661,7 +1661,7 @@ changes:
16611661
Creates a symbolic link.
16621662
16631663
The `type` argument is only used on Windows platforms and can be one of `'dir'`,
1664-
`'file'`, or `'junction'`. If the `type` argument is not a string, Node.js will
1664+
`'file'`, or `'junction'`. If the `type` argument is `null`, Node.js will
16651665
autodetect `target` type and use `'file'` or `'dir'`. If the `target` does not
16661666
exist, `'file'` will be used. Windows junction points require the destination
16671667
path to be absolute. When using `'junction'`, the `target` argument will
@@ -4374,7 +4374,7 @@ See the POSIX symlink(2) documentation for more details.
43744374
43754375
The `type` argument is only available on Windows and ignored on other platforms.
43764376
It can be set to `'dir'`, `'file'`, or `'junction'`. If the `type` argument is
4377-
not a string, Node.js will autodetect `target` type and use `'file'` or `'dir'`.
4377+
`null`, Node.js will autodetect `target` type and use `'file'` or `'dir'`.
43784378
If the `target` does not exist, `'file'` will be used. Windows junction points
43794379
require the destination path to be absolute. When using `'junction'`, the
43804380
`target` argument will automatically be normalized to absolute path. Junction

lib/fs.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ const {
141141
validateFunction,
142142
validateInteger,
143143
validateObject,
144+
validateOneOf,
144145
validateString,
145146
kValidateObjectAllowNullable,
146147
} = require('internal/validators');
@@ -1714,13 +1715,17 @@ function readlinkSync(path, options) {
17141715
* Creates the link called `path` pointing to `target`.
17151716
* @param {string | Buffer | URL} target
17161717
* @param {string | Buffer | URL} path
1717-
* @param {string | null} [type_]
1718-
* @param {(err?: Error) => any} callback_
1718+
* @param {string | null} [type]
1719+
* @param {(err?: Error) => any} callback
17191720
* @returns {void}
17201721
*/
1721-
function symlink(target, path, type_, callback_) {
1722-
const type = (typeof type_ === 'string' ? type_ : null);
1723-
const callback = makeCallback(arguments[arguments.length - 1]);
1722+
function symlink(target, path, type, callback) {
1723+
if (callback === undefined) {
1724+
callback = makeCallback(type);
1725+
type = undefined;
1726+
} else {
1727+
validateOneOf(type, 'type', ['dir', 'file', 'junction', null, undefined]);
1728+
}
17241729

17251730
if (permission.isEnabled()) {
17261731
// The permission model's security guarantees fall apart in the presence of
@@ -1739,7 +1744,7 @@ function symlink(target, path, type_, callback_) {
17391744
target = getValidatedPath(target, 'target');
17401745
path = getValidatedPath(path);
17411746

1742-
if (isWindows && type === null) {
1747+
if (isWindows && type == null) {
17431748
let absoluteTarget;
17441749
try {
17451750
// Symlinks targets can be relative to the newly created path.
@@ -1785,8 +1790,8 @@ function symlink(target, path, type_, callback_) {
17851790
* @returns {void}
17861791
*/
17871792
function symlinkSync(target, path, type) {
1788-
type = (typeof type === 'string' ? type : null);
1789-
if (isWindows && type === null) {
1793+
validateOneOf(type, 'type', ['dir', 'file', 'junction', null, undefined]);
1794+
if (isWindows && type == null) {
17901795
const absoluteTarget = pathModule.resolve(`${path}`, '..', `${target}`);
17911796
if (statSync(absoluteTarget, { throwIfNoEntry: false })?.isDirectory()) {
17921797
type = 'dir';

lib/internal/errors.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,9 +1217,6 @@ E('ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY',
12171217
E('ERR_FS_CP_UNKNOWN', 'Cannot copy an unknown file type', SystemError);
12181218
E('ERR_FS_EISDIR', 'Path is a directory', SystemError, HideStackFramesError);
12191219
E('ERR_FS_FILE_TOO_LARGE', 'File size (%s) is greater than 2 GiB', RangeError);
1220-
E('ERR_FS_INVALID_SYMLINK_TYPE',
1221-
'Symlink type must be one of "dir", "file", or "junction". Received "%s"',
1222-
Error); // Switch to TypeError. The current implementation does not seem right
12231220
E('ERR_HTTP2_ALTSVC_INVALID_ORIGIN',
12241221
'HTTP/2 ALTSVC frames require a valid origin', TypeError);
12251222
E('ERR_HTTP2_ALTSVC_LENGTH',

lib/internal/fs/promises.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ const {
8383
validateEncoding,
8484
validateInteger,
8585
validateObject,
86+
validateOneOf,
8687
validateString,
8788
kValidateObjectAllowNullable,
8889
} = require('internal/validators');
@@ -972,9 +973,9 @@ async function readlink(path, options) {
972973
);
973974
}
974975

975-
async function symlink(target, path, type_) {
976-
let type = (typeof type_ === 'string' ? type_ : null);
977-
if (isWindows && type === null) {
976+
async function symlink(target, path, type) {
977+
validateOneOf(type, 'type', ['dir', 'file', 'junction', null, undefined]);
978+
if (isWindows && type == null) {
978979
try {
979980
const absoluteTarget = pathModule.resolve(`${path}`, '..', `${target}`);
980981
type = (await stat(absoluteTarget)).isDirectory() ? 'dir' : 'file';

lib/internal/fs/utils.js

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ const { Buffer } = require('buffer');
3333
const {
3434
codes: {
3535
ERR_FS_EISDIR,
36-
ERR_FS_INVALID_SYMLINK_TYPE,
3736
ERR_INCOMPATIBLE_OPTION_PAIR,
3837
ERR_INVALID_ARG_TYPE,
3938
ERR_INVALID_ARG_VALUE,
@@ -651,22 +650,16 @@ function stringToFlags(flags, name = 'flags') {
651650
}
652651

653652
const stringToSymlinkType = hideStackFrames((type) => {
654-
let flags = 0;
655-
if (typeof type === 'string') {
656-
switch (type) {
657-
case 'dir':
658-
flags |= UV_FS_SYMLINK_DIR;
659-
break;
660-
case 'junction':
661-
flags |= UV_FS_SYMLINK_JUNCTION;
662-
break;
663-
case 'file':
664-
break;
665-
default:
666-
throw new ERR_FS_INVALID_SYMLINK_TYPE(type);
667-
}
653+
switch (type) {
654+
case undefined:
655+
case null:
656+
case 'file':
657+
return 0;
658+
case 'dir':
659+
return UV_FS_SYMLINK_DIR;
660+
case 'junction':
661+
return UV_FS_SYMLINK_JUNCTION;
668662
}
669-
return flags;
670663
});
671664

672665
// converts Date or number to a fractional UNIX timestamp

test/parallel/test-fs-symlink.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,27 @@ fs.symlink(linkData, linkPath, common.mustSucceed(() => {
7676
});
7777

7878
const errObj = {
79-
code: 'ERR_FS_INVALID_SYMLINK_TYPE',
80-
name: 'Error',
81-
message:
82-
'Symlink type must be one of "dir", "file", or "junction". Received "🍏"'
79+
code: 'ERR_INVALID_ARG_VALUE',
80+
name: 'TypeError',
8381
};
8482
assert.throws(() => fs.symlink('', '', '🍏', common.mustNotCall()), errObj);
8583
assert.throws(() => fs.symlinkSync('', '', '🍏'), errObj);
8684

85+
assert.throws(() => fs.symlink('', '', 'nonExistentType', common.mustNotCall()), errObj);
86+
assert.throws(() => fs.symlinkSync('', '', 'nonExistentType'), errObj);
87+
assert.rejects(() => fs.promises.symlink('', '', 'nonExistentType'), errObj)
88+
.then(common.mustCall());
89+
90+
assert.throws(() => fs.symlink('', '', false, common.mustNotCall()), errObj);
91+
assert.throws(() => fs.symlinkSync('', '', false), errObj);
92+
assert.rejects(() => fs.promises.symlink('', '', false), errObj)
93+
.then(common.mustCall());
94+
95+
assert.throws(() => fs.symlink('', '', {}, common.mustNotCall()), errObj);
96+
assert.throws(() => fs.symlinkSync('', '', {}), errObj);
97+
assert.rejects(() => fs.promises.symlink('', '', {}), errObj)
98+
.then(common.mustCall());
99+
87100
process.on('exit', () => {
88101
assert.notStrictEqual(linkTime, fileTime);
89102
});

0 commit comments

Comments
 (0)