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: allow file: URL strings as paths #20944

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/internal/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -1393,7 +1393,11 @@ function getPathFromURLPosix(url) {
}

function getPathFromURL(path) {
if (path == null || !path[searchParams] ||
if (typeof path === 'string') {
if (!path.startsWith('file://'))
return path;
path = new URL(path);
} else if (path == null || !path[searchParams] ||
!path[searchParams][searchParams]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This no longer lines up with the rest of the conditional above this line.

return path;
}
Expand Down
63 changes: 63 additions & 0 deletions test/parallel/test-fs-null-bytes.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,69 @@ check(null, fs.watch, fileUrl2, assert.fail);
check(null, fs.watchFile, fileUrl2, assert.fail);
check(fs.writeFile, fs.writeFileSync, fileUrl2, 'abc');

const fileUrlString = new URL('file:///C:/foo\u0000bar').href;
const fileUrl2String = new URL('file:///C:/foo%00bar').href;

check(fs.access, fs.accessSync, fileUrlString);
check(fs.access, fs.accessSync, fileUrlString, fs.F_OK);
check(fs.appendFile, fs.appendFileSync, fileUrlString, 'abc');
check(fs.chmod, fs.chmodSync, fileUrlString, '0644');
check(fs.chown, fs.chownSync, fileUrlString, 12, 34);
check(fs.copyFile, fs.copyFileSync, fileUrlString, 'abc');
check(fs.copyFile, fs.copyFileSync, 'abc', fileUrlString);
check(fs.link, fs.linkSync, fileUrlString, 'foobar');
check(fs.link, fs.linkSync, 'foobar', fileUrlString);
check(fs.lstat, fs.lstatSync, fileUrlString);
check(fs.mkdir, fs.mkdirSync, fileUrlString, '0755');
check(fs.open, fs.openSync, fileUrlString, 'r');
check(fs.readFile, fs.readFileSync, fileUrlString);
check(fs.readdir, fs.readdirSync, fileUrlString);
check(fs.readlink, fs.readlinkSync, fileUrlString);
check(fs.realpath, fs.realpathSync, fileUrlString);
check(fs.rename, fs.renameSync, fileUrlString, 'foobar');
check(fs.rename, fs.renameSync, 'foobar', fileUrlString);
check(fs.rmdir, fs.rmdirSync, fileUrlString);
check(fs.stat, fs.statSync, fileUrlString);
check(fs.symlink, fs.symlinkSync, fileUrlString, 'foobar');
check(fs.symlink, fs.symlinkSync, 'foobar', fileUrlString);
check(fs.truncate, fs.truncateSync, fileUrlString);
check(fs.unlink, fs.unlinkSync, fileUrlString);
check(null, fs.unwatchFile, fileUrlString, assert.fail);
check(fs.utimes, fs.utimesSync, fileUrlString, 0, 0);
check(null, fs.watch, fileUrlString, assert.fail);
check(null, fs.watchFile, fileUrlString, assert.fail);
check(fs.writeFile, fs.writeFileSync, fileUrlString, 'abc');

check(fs.access, fs.accessSync, fileUrl2String);
check(fs.access, fs.accessSync, fileUrl2String, fs.F_OK);
check(fs.appendFile, fs.appendFileSync, fileUrl2String, 'abc');
check(fs.chmod, fs.chmodSync, fileUrl2String, '0644');
check(fs.chown, fs.chownSync, fileUrl2String, 12, 34);
check(fs.copyFile, fs.copyFileSync, fileUrl2String, 'abc');
check(fs.copyFile, fs.copyFileSync, 'abc', fileUrl2String);
check(fs.link, fs.linkSync, fileUrl2String, 'foobar');
check(fs.link, fs.linkSync, 'foobar', fileUrl2String);
check(fs.lstat, fs.lstatSync, fileUrl2String);
check(fs.mkdir, fs.mkdirSync, fileUrl2String, '0755');
check(fs.open, fs.openSync, fileUrl2String, 'r');
check(fs.readFile, fs.readFileSync, fileUrl2String);
check(fs.readdir, fs.readdirSync, fileUrl2String);
check(fs.readlink, fs.readlinkSync, fileUrl2String);
check(fs.realpath, fs.realpathSync, fileUrl2String);
check(fs.rename, fs.renameSync, fileUrl2String, 'foobar');
check(fs.rename, fs.renameSync, 'foobar', fileUrl2String);
check(fs.rmdir, fs.rmdirSync, fileUrl2String);
check(fs.stat, fs.statSync, fileUrl2String);
check(fs.symlink, fs.symlinkSync, fileUrl2String, 'foobar');
check(fs.symlink, fs.symlinkSync, 'foobar', fileUrl2String);
check(fs.truncate, fs.truncateSync, fileUrl2String);
check(fs.unlink, fs.unlinkSync, fileUrl2String);
check(null, fs.unwatchFile, fileUrl2String, assert.fail);
check(fs.utimes, fs.utimesSync, fileUrl2String, 0, 0);
check(null, fs.watch, fileUrl2String, assert.fail);
check(null, fs.watchFile, fileUrl2String, assert.fail);
check(fs.writeFile, fs.writeFileSync, fileUrl2String, 'abc');

// an 'error' for exists means that it doesn't exist.
// one of many reasons why this file is the absolute worst.
fs.exists('foo\u0000bar', common.mustCall((exists) => {
Expand Down
68 changes: 68 additions & 0 deletions test/parallel/test-fs-whatwg-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ fs.readFile(url, common.mustCall((err, data) => {
assert(Buffer.isBuffer(data));
}));

// Check that we can pass in a URL string successfully
fs.readFile(url.href, common.mustCall((err, data) => {
assert.ifError(err);
assert(Buffer.isBuffer(data));
}));

// Check that using a non file:// URL reports an error
const httpUrl = new URL('http://example.org');

Expand All @@ -40,6 +46,16 @@ common.expectsError(
message: 'The URL must be of scheme file'
});

common.expectsError(
() => {
fs.readFileSync(httpUrl.href);
},
{
code: 'ENOENT',
type: Error,
message: 'ENOENT: no such file or directory, open \'http://example.org/\''
});

// pct-encoded characters in the path will be decoded and checked
if (common.isWindows) {
// encoded back and forward slashes are not permitted on windows
Expand All @@ -54,6 +70,17 @@ if (common.isWindows) {
message: 'File URL path must not include encoded \\ or / characters'
}
);

common.expectsError(
() => {
fs.readFile(new URL(`file:///c:/tmp/${i}`).href, common.mustNotCall());
},
{
code: 'ERR_INVALID_FILE_URL_PATH',
type: TypeError,
message: 'File URL path must not include encoded \\ or / characters'
}
);
});
common.expectsError(
() => {
Expand All @@ -66,6 +93,17 @@ if (common.isWindows) {
'null bytes. Received \'c:/tmp/\\u0000test\''
}
);
common.expectsError(
() => {
fs.readFile(new URL('file:///c:/tmp/%00test').href, common.mustNotCall());
},
{
code: 'ERR_INVALID_ARG_VALUE',
type: TypeError,
message: 'The argument \'path\' must be a string or Uint8Array without ' +
'null bytes. Received \'c:/tmp/\\u0000test\''
}
);
} else {
// encoded forward slashes are not permitted on other platforms
['%2f', '%2F'].forEach((i) => {
Expand All @@ -78,6 +116,15 @@ if (common.isWindows) {
type: TypeError,
message: 'File URL path must not include encoded / characters'
});
common.expectsError(
() => {
fs.readFile(new URL(`file:///c:/tmp/${i}`).href, common.mustNotCall());
},
{
code: 'ERR_INVALID_FILE_URL_PATH',
type: TypeError,
message: 'File URL path must not include encoded / characters'
});
});
common.expectsError(
() => {
Expand All @@ -89,6 +136,16 @@ if (common.isWindows) {
message: `File URL host must be "localhost" or empty on ${os.platform()}`
}
);
common.expectsError(
() => {
fs.readFile(new URL('file://hostname/a/b/c').href, common.mustNotCall());
},
{
code: 'ERR_INVALID_FILE_URL_HOST',
type: TypeError,
message: `File URL host must be "localhost" or empty on ${os.platform()}`
}
);
common.expectsError(
() => {
fs.readFile(new URL('file:///tmp/%00test'), common.mustNotCall());
Expand All @@ -100,4 +157,15 @@ if (common.isWindows) {
'null bytes. Received \'/tmp/\\u0000test\''
}
);
common.expectsError(
() => {
fs.readFile(new URL('file:///tmp/%00test').href, common.mustNotCall());
},
{
code: 'ERR_INVALID_ARG_VALUE',
type: TypeError,
message: 'The argument \'path\' must be a string or Uint8Array without ' +
'null bytes. Received \'/tmp/\\u0000test\''
}
);
}