Skip to content

Commit

Permalink
fs: adjust typecheck for type in fs.symlink()
Browse files Browse the repository at this point in the history
Throws `TypeError` instead of `Error`
Enables autodetection on Windows if `type === undefined`
Explicitly disallows unknown strings and non-string values
  • Loading branch information
LiviaMedeiros committed Feb 26, 2024
1 parent 4556445 commit bcfb9bd
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 36 deletions.
4 changes: 2 additions & 2 deletions doc/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1661,7 +1661,7 @@ changes:
Creates a symbolic link.
The `type` argument is only used on Windows platforms and can be one of `'dir'`,
`'file'`, or `'junction'`. If the `type` argument is not a string, Node.js will
`'file'`, or `'junction'`. If the `type` argument is `null`, Node.js will
autodetect `target` type and use `'file'` or `'dir'`. If the `target` does not
exist, `'file'` will be used. Windows junction points require the destination
path to be absolute. When using `'junction'`, the `target` argument will
Expand Down Expand Up @@ -4374,7 +4374,7 @@ See the POSIX symlink(2) documentation for more details.
The `type` argument is only available on Windows and ignored on other platforms.
It can be set to `'dir'`, `'file'`, or `'junction'`. If the `type` argument is
not a string, Node.js will autodetect `target` type and use `'file'` or `'dir'`.
`null`, Node.js will autodetect `target` type and use `'file'` or `'dir'`.
If the `target` does not exist, `'file'` will be used. Windows junction points
require the destination path to be absolute. When using `'junction'`, the
`target` argument will automatically be normalized to absolute path. Junction
Expand Down
21 changes: 13 additions & 8 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ const {
validateFunction,
validateInteger,
validateObject,
validateOneOf,
validateString,
kValidateObjectAllowNullable,
} = require('internal/validators');
Expand Down Expand Up @@ -1714,13 +1715,17 @@ function readlinkSync(path, options) {
* Creates the link called `path` pointing to `target`.
* @param {string | Buffer | URL} target
* @param {string | Buffer | URL} path
* @param {string | null} [type_]
* @param {(err?: Error) => any} callback_
* @param {string | null} [type]
* @param {(err?: Error) => any} callback
* @returns {void}
*/
function symlink(target, path, type_, callback_) {
const type = (typeof type_ === 'string' ? type_ : null);
const callback = makeCallback(arguments[arguments.length - 1]);
function symlink(target, path, type, callback) {
if (callback === undefined) {
callback = makeCallback(type);
type = undefined;
} else {
validateOneOf(type, 'type', ['dir', 'file', 'junction', null, undefined]);
}

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

if (isWindows && type === null) {
if (isWindows && type == null) {
let absoluteTarget;
try {
// Symlinks targets can be relative to the newly created path.
Expand Down Expand Up @@ -1785,8 +1790,8 @@ function symlink(target, path, type_, callback_) {
* @returns {void}
*/
function symlinkSync(target, path, type) {
type = (typeof type === 'string' ? type : null);
if (isWindows && type === null) {
validateOneOf(type, 'type', ['dir', 'file', 'junction', null, undefined]);
if (isWindows && type == null) {
const absoluteTarget = pathModule.resolve(`${path}`, '..', `${target}`);
if (statSync(absoluteTarget, { throwIfNoEntry: false })?.isDirectory()) {
type = 'dir';
Expand Down
3 changes: 0 additions & 3 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1217,9 +1217,6 @@ E('ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY',
E('ERR_FS_CP_UNKNOWN', 'Cannot copy an unknown file type', SystemError);
E('ERR_FS_EISDIR', 'Path is a directory', SystemError, HideStackFramesError);
E('ERR_FS_FILE_TOO_LARGE', 'File size (%s) is greater than 2 GiB', RangeError);
E('ERR_FS_INVALID_SYMLINK_TYPE',
'Symlink type must be one of "dir", "file", or "junction". Received "%s"',
Error); // Switch to TypeError. The current implementation does not seem right
E('ERR_HTTP2_ALTSVC_INVALID_ORIGIN',
'HTTP/2 ALTSVC frames require a valid origin', TypeError);
E('ERR_HTTP2_ALTSVC_LENGTH',
Expand Down
7 changes: 4 additions & 3 deletions lib/internal/fs/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ const {
validateEncoding,
validateInteger,
validateObject,
validateOneOf,
validateString,
kValidateObjectAllowNullable,
} = require('internal/validators');
Expand Down Expand Up @@ -972,9 +973,9 @@ async function readlink(path, options) {
);
}

async function symlink(target, path, type_) {
let type = (typeof type_ === 'string' ? type_ : null);
if (isWindows && type === null) {
async function symlink(target, path, type) {
validateOneOf(type, 'type', ['dir', 'file', 'junction', null, undefined]);
if (isWindows && type == null) {
try {
const absoluteTarget = pathModule.resolve(`${path}`, '..', `${target}`);
type = (await stat(absoluteTarget)).isDirectory() ? 'dir' : 'file';
Expand Down
25 changes: 9 additions & 16 deletions lib/internal/fs/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ const { Buffer } = require('buffer');
const {
codes: {
ERR_FS_EISDIR,
ERR_FS_INVALID_SYMLINK_TYPE,
ERR_INCOMPATIBLE_OPTION_PAIR,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
Expand Down Expand Up @@ -651,22 +650,16 @@ function stringToFlags(flags, name = 'flags') {
}

const stringToSymlinkType = hideStackFrames((type) => {
let flags = 0;
if (typeof type === 'string') {
switch (type) {
case 'dir':
flags |= UV_FS_SYMLINK_DIR;
break;
case 'junction':
flags |= UV_FS_SYMLINK_JUNCTION;
break;
case 'file':
break;
default:
throw new ERR_FS_INVALID_SYMLINK_TYPE(type);
}
switch (type) {
case undefined:
case null:
case 'file':
return 0;
case 'dir':
return UV_FS_SYMLINK_DIR;
case 'junction':
return UV_FS_SYMLINK_JUNCTION;
}
return flags;
});

// converts Date or number to a fractional UNIX timestamp
Expand Down
21 changes: 17 additions & 4 deletions test/parallel/test-fs-symlink.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,27 @@ fs.symlink(linkData, linkPath, common.mustSucceed(() => {
});

const errObj = {
code: 'ERR_FS_INVALID_SYMLINK_TYPE',
name: 'Error',
message:
'Symlink type must be one of "dir", "file", or "junction". Received "🍏"'
code: 'ERR_INVALID_ARG_VALUE',
name: 'TypeError',
};
assert.throws(() => fs.symlink('', '', '🍏', common.mustNotCall()), errObj);
assert.throws(() => fs.symlinkSync('', '', '🍏'), errObj);

assert.throws(() => fs.symlink('', '', 'nonExistentType', common.mustNotCall()), errObj);
assert.throws(() => fs.symlinkSync('', '', 'nonExistentType'), errObj);
assert.rejects(() => fs.promises.symlink('', '', 'nonExistentType'), errObj)
.then(common.mustCall());

assert.throws(() => fs.symlink('', '', false, common.mustNotCall()), errObj);
assert.throws(() => fs.symlinkSync('', '', false), errObj);
assert.rejects(() => fs.promises.symlink('', '', false), errObj)
.then(common.mustCall());

assert.throws(() => fs.symlink('', '', {}, common.mustNotCall()), errObj);
assert.throws(() => fs.symlinkSync('', '', {}), errObj);
assert.rejects(() => fs.promises.symlink('', '', {}), errObj)
.then(common.mustCall());

process.on('exit', () => {
assert.notStrictEqual(linkTime, fileTime);
});

0 comments on commit bcfb9bd

Please sign in to comment.