Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs: adjust typecheck for type in fs.symlink() #49741

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 11 additions & 7 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -1377,13 +1377,6 @@ Path is a directory.
An attempt has been made to read a file whose size is larger than the maximum
allowed size for a `Buffer`.

<a id="ERR_FS_INVALID_SYMLINK_TYPE"></a>

### `ERR_FS_INVALID_SYMLINK_TYPE`

An invalid symlink type was passed to the [`fs.symlink()`][] or
[`fs.symlinkSync()`][] methods.

<a id="ERR_HTTP_HEADERS_SENT"></a>

### `ERR_HTTP_HEADERS_SENT`
Expand Down Expand Up @@ -3276,6 +3269,17 @@ The UTF-16 encoding was used with [`hash.digest()`][]. While the
causing the method to return a string rather than a `Buffer`, the UTF-16
encoding (e.g. `ucs` or `utf16le`) is not supported.

<a id="ERR_FS_INVALID_SYMLINK_TYPE"></a>

### `ERR_FS_INVALID_SYMLINK_TYPE`

<!-- YAML
removed: REPLACEME
-->

An invalid symlink type was passed to the [`fs.symlink()`][] or
[`fs.symlinkSync()`][] methods.

<a id="ERR_HTTP2_FRAME_ERROR"></a>

### `ERR_HTTP2_FRAME_ERROR`
Expand Down
4 changes: 2 additions & 2 deletions doc/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1693,7 +1693,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 @@ -4444,7 +4444,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 @@ -142,6 +142,7 @@ const {
validateFunction,
validateInteger,
validateObject,
validateOneOf,
validateString,
kValidateObjectAllowNullable,
} = require('internal/validators');
Expand Down Expand Up @@ -1715,13 +1716,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 @@ -1740,7 +1745,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 @@ -1786,8 +1791,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) {
anonrig marked this conversation as resolved.
Show resolved Hide resolved
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
aduh95 marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -85,6 +85,7 @@ const {
validateEncoding,
validateInteger,
validateObject,
validateOneOf,
validateString,
kValidateObjectAllowNullable,
} = require('internal/validators');
Expand Down Expand Up @@ -973,9 +974,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 @@ -31,7 +31,6 @@ const {
UVException,
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 @@ -647,22 +646,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;
Comment on lines +649 to +657
Copy link

@Mifrill Mifrill Oct 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
switch (type) {
case undefined:
case null:
case 'file':
return 0;
case 'dir':
return UV_FS_SYMLINK_DIR;
case 'junction':
return UV_FS_SYMLINK_JUNCTION;
const linkTypes = {
undefined: 0,
null: 0,
file: 0,
dir: UV_FS_SYMLINK_DIR,
junction: UV_FS_SYMLINK_JUNCTION
};
return linkTypes[type] || 0;

The || 0 is a fallback in case the type is not found in the linkTypes object. If type is not one of the defined values in the linkTypes object, linkType will default to 0. This ensures that linkType always has a value and doesn't become undefined. It's a way to handle unexpected or unknown values for type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We intentionally don't fallback to file. If an unknown type is passed, we must throw TypeError, which is done by validateOneOf().

}
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);
});