-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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: create consistent behavior for fs module across platforms #17927
Conversation
lib/fs.js
Outdated
@@ -385,6 +386,11 @@ fs.readFile = function(path, options, callback) { | |||
if (!nullCheck(path, callback)) | |||
return; | |||
|
|||
//doing justice between linux and windows for `/` char at the end of file path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
space after comment please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@devsnek Space added thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think what @devsnek meant was a space after the comment indicator (//
) so:
// doing justice...
There are some lint errors you're going to want to correct (mostly around line lengths). These should have been flagged if you ran make test
or vcbuild test
. To just run the JS linting (since running the full tests can be time-consuming), you can use make lint-js
or vcbuild lint-js
.
Lastly, welcome @timotew and thanks for the pull request!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/fs.js
Outdated
@@ -343,6 +343,7 @@ fs.exists = function(path, callback) { | |||
if (typeof path !== 'string' && !(path instanceof Buffer)) { | |||
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', | |||
['string', 'Buffer', 'URL']); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This empty line is redundant :-)
lib/fs.js
Outdated
// doing justice between linux and windows | ||
// for `/` char at the end of file path | ||
if (isWindows && (path.slice(-1) === '/')) { | ||
throw new errors.Error('ERR_INVALID_FILE_URL_PATH', path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Using
errors.TypeError
might be more appropriate. - The second parameter (here is
path
) ofERR_INVALID_FILE_URL_PATH
is wrong. It should be a string contains an error description about the invalid path.
Here is some example:
Lines 1362 to 1363 in abe14ab
return new errors.TypeError('ERR_INVALID_FILE_URL_PATH', 'must not include encoded / characters');
Lines 1346 to 1347 in abe14ab
return new errors.TypeError('ERR_INVALID_FILE_URL_PATH', 'must be absolute');
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think errors.TypeError
is appropriate here. A TypeError
would be if we were expecting a string but received something else. In this case, we are expecting a string and receiving a string, but it has a value that is not valid. (By all means, please correct me if I'm wrong or misunderstanding something!)
The second point (about the second parameter) is 👍.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now the similar ERR_INVALID_FILE_URL_PATH
errors in internal/url.js
are using TypeError
(some examples showed above).
I do agree with your point. For semantics, using Error
is better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the correction it's effected. I am still not clear about the error type @Trott .
From what @starkwang showed above, Please I might also be wrong but I think TypeError
is appropriate for the case.
errors in internal/url.js
are expecting characters that should not include some special characters, The type there might be referring to character type not data type.
lib/fs.js
Outdated
// for `/` char at the end of file path | ||
if (isWindows && (path.slice(-1) === '/')) { | ||
throw new errors.Error('ERR_INVALID_FILE_URL_PATH', | ||
'file path must not end with / character'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This string should be reduced into 'must not end with / character'
The first two words 'file path'
is redundant because these words were already contained in template:
Line 401 in a82b1b7
E('ERR_INVALID_FILE_URL_PATH', 'File URL path %s'); |
test/parallel/test-fs-whatwg-url.js
Outdated
fs.readFile(new URL(`file:///c:/tmp/${i}/`), common.expectsError({ | ||
code: 'ERR_INVALID_FILE_URL_PATH', | ||
type: TypeError, | ||
message: 'File URL path can not end with / characters' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Umm... If I'm not mistaken, the message
option will not match the actual error message.
It should be 'File URL path must not end with / character'
.
By the way, you can use ./configure && make -j4 test
on Unix/MacOS or vcbuild test
on Windows to run the tests on your computer (For more guides, you can refer to here). PR should always pass all the tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@starkwang Thanks a lot.
doc/api/fs.md
Outdated
@@ -202,6 +202,10 @@ fs.readFileSync(new URL('file:///C:/tmp/hello')); | |||
fs.readFileSync(new URL('file:///notdriveletter/p/a/t/h/file')); | |||
fs.readFileSync(new URL('file:///c/p/a/t/h/file')); | |||
// TypeError [ERR_INVALID_FILE_URL_PATH]: File URL path must be absolute | |||
|
|||
// - WHATWG file URLs must not end with forward slash `/` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove the leading -
lib/fs.js
Outdated
@@ -385,6 +385,14 @@ fs.readFile = function(path, options, callback) { | |||
if (!nullCheck(path, callback)) | |||
return; | |||
|
|||
// doing justice between linux and windows | |||
// for `/` char at the end of file path | |||
if (isWindows && (typeof path === 'string')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the parens around the typeof
check are unnecessary.
lib/fs.js
Outdated
// doing justice between linux and windows | ||
// for `/` char at the end of file path | ||
if (isWindows && (typeof path === 'string')) { | ||
if (path.slice(-1) === '/') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no need for a nested if
.
lib/fs.js
Outdated
// doing justice between linux and windows | ||
// for `/` char at the end of file path | ||
if (isWindows && (typeof path === 'string')) { | ||
if (path.slice(-1) === '/') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be worth testing if slice()
is faster than charAt()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cjihrig charAt()
is faster from my test, Thanks.
@nodejs/fs |
Might be worth a look from @nodejs/url too, especially @TimothyGu? |
lib/fs.js
Outdated
// doing justice between linux and windows | ||
// for `/` char at the end of file path | ||
if (isWindows && typeof path === 'string' && | ||
path.charAt(path.length - 1) === '/') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
path.charAt()
on this line should be aligned with isWindows
on the previous line.
lib/fs.js
Outdated
@@ -385,6 +385,13 @@ fs.readFile = function(path, options, callback) { | |||
if (!nullCheck(path, callback)) | |||
return; | |||
|
|||
// doing justice between linux and windows |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that "doing justice" makes a lot of sense here.
If #17801 is about inconsistent behavior on different platforms, this still does not resolves that since different errors are thrown in different platforms. Also this just fixes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Making it explicit.
@joyeecheung Hi there! hope you have time to review these change. |
test/parallel/test-fs-whatwg-url.js
Outdated
@@ -72,3 +72,10 @@ if (common.isWindows) { | |||
message: `File URL host must be "localhost" or empty on ${os.platform()}` | |||
})); | |||
} | |||
|
|||
// path ending with forward slash are not permitted | |||
fs.readFile(new URL('file:///c:/tmp/test/'), common.expectsError({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add tests for other APIs with the validation changed as well? Also it would be more robust if we test paths like file:///tmp/test/
on non-windows systems because file:///c:/tmp/test/
is obviously invalid there for a different reason(drives).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can test different type of URLs on different systems by reusing the tested URL object like this:
const urlWithSlash = common.isWindows ?
new URL('file:///c:/tmp/test/') : new URL('file:///tmp/test/');
fs.readFile(urlWithSlash, common.expectsError(...));
lib/internal/url.js
Outdated
if (path == null || !path[searchParams] || | ||
!path[searchParams][searchParams]) { | ||
return path; | ||
} | ||
if (path.protocol !== 'file:') | ||
return new errors.TypeError('ERR_INVALID_URL_SCHEME', 'file'); | ||
if (strictPath && typeof path.href === 'string' && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The typeof path.href === 'string'
should be unnecessary because a valid WHATWG URL should always have a string as href
lib/internal/url.js
Outdated
@@ -1367,13 +1367,18 @@ function getPathFromURLPosix(url) { | |||
return decodeURIComponent(pathname); | |||
} | |||
|
|||
function getPathFromURL(path) { | |||
function getPathFromURL(path, strictPath) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's more accurate to name this assertFilename
or something similar.
lib/internal/url.js
Outdated
if (strictPath && typeof path.href === 'string' && | ||
path.href.charAt(path.href.length - 1) === '/') { | ||
return new errors.Error('ERR_INVALID_FILE_URL_PATH', | ||
'must not end with / character'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The message Illegal operation on a directory
is actually more accurate here.
Nice work, although I think there are still some APIs that need to be updated:
Can you update those APIs as well? |
ping @joyeecheung . |
@timotew Can you add tests for the changed APIs? Thanks! |
@joyeecheung please review these test cases. Thanks! |
test/parallel/test-fs-whatwg-url.js
Outdated
message: 'File URL path illegal operation on a directory' | ||
}); | ||
|
||
//fs.open |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the linter pass on this? I think it should complain on comments without a space after //
?
test/parallel/test-fs-whatwg-url.js
Outdated
//creatReadStream | ||
common.expectsError( | ||
() => { | ||
fs.createReadStream(new URL('file:///c:/tmp/test/'), 'test data'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The second argument of fs.createReadStream
is an option object or an encoding, I am pretty sure this would result in an error. We can leave it as fs.createReadStream(urlWithSlash)
here. The same goes to fs.createWriteStream
as well
test/parallel/test-fs-whatwg-url.js
Outdated
@@ -72,3 +72,10 @@ if (common.isWindows) { | |||
message: `File URL host must be "localhost" or empty on ${os.platform()}` | |||
})); | |||
} | |||
|
|||
// path ending with forward slash are not permitted | |||
fs.readFile(new URL('file:///c:/tmp/test/'), common.expectsError({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can test different type of URLs on different systems by reusing the tested URL object like this:
const urlWithSlash = common.isWindows ?
new URL('file:///c:/tmp/test/') : new URL('file:///tmp/test/');
fs.readFile(urlWithSlash, common.expectsError(...));
@joyeecheung Please let me know if you notice any outstanding API. |
Just realized this PR only normalizes the behavior for |
lib/fs.js
Outdated
@@ -1818,7 +1826,7 @@ StatWatcher.prototype.stop = function() { | |||
const statWatchers = new Map(); | |||
|
|||
fs.watchFile = function(filename, options, listener) { | |||
handleError((filename = getPathFromURL(filename))); | |||
handleError((filename = getPathFromURL(filename, true))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We actually support fs.watchFile
and fs.unwatchFile
on directories. There are even tests in the async-hook test suites.
lib/fs.js
Outdated
@@ -297,7 +297,7 @@ fs.access = function(path, mode, callback) { | |||
throw new errors.TypeError('ERR_INVALID_CALLBACK'); | |||
} | |||
|
|||
if (handleError((path = getPathFromURL(path)), callback)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fs.access
is supported on directories.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, did you mean to change fs.append
?
lib/fs.js
Outdated
@@ -1112,7 +1116,7 @@ fs.lstat = function(path, callback) { | |||
|
|||
fs.stat = function(path, callback) { | |||
callback = makeStatsCallback(callback); | |||
if (handleError((path = getPathFromURL(path)), callback)) | |||
if (handleError((path = getPathFromURL(path, true)), callback)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fs.stat
is supported on directories.
lib/fs.js
Outdated
@@ -1312,7 +1316,7 @@ fs.linkSync = function(existingPath, newPath) { | |||
|
|||
fs.unlink = function(path, callback) { | |||
callback = makeCallback(callback); | |||
if (handleError((path = getPathFromURL(path)), callback)) | |||
if (handleError((path = getPathFromURL(path, true)), callback)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This supports directories as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your patience on this PR!
Just some small nits.
test/parallel/test-fs-whatwg-url.js
Outdated
fs.readFile(urlWithSlash, common.expectsError({ | ||
code: 'ERR_INVALID_FILE_URL_PATH', | ||
type: Error, | ||
message: 'File URL path illegal operation on a directory' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This message seems to contain some syntax errors. Maybe 'File URL path cannot be a directory'
?
Just for future reference, we typically use |
@timotew You can rebase your branch by doing something like this:
Looking at the commit history of this branch I think you have mixed commits from the upstream with your own commits. One way to get those back: git checkout your-pr-branch
git checkout -b backup-branch # back up where we are right now
git fetch upstream master
git checkout -b branch-for-cleanup # create a new branch to clean things up
git reset --hard upstream/master # bring the cleanup branch up-to-date with upstream
git log --reverse --pretty=format:%H --author="Your User Name" your-pr-branch | xargs -n 1 git cherry-pick
git checkout your-pr-branch
git reset --hard branch-for-cleanup # reset your pr branch to the same state as the cleanup branch Where If you have trouble doing the above, let me know and I can help you restore the commits back (I can push to this PR branch since you allowed edits from maintainers in this PR). |
27d7d4c
to
49ef465
Compare
@joyeecheung Thanks a lot! |
test/parallel/test-fs-path-err.js
Outdated
const urlWithoutSlash = new URL(stringWithoutSlash); | ||
|
||
// Uint8Array Buffer | ||
const U8ABufferWithSlash = common.isWindows ? new Uint8Array([ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use new Uint8Array(Buffer.from('....'))
here to make it more readable.
doc/api/errors.md
Outdated
<a id="ERR_INVALID_PATH_FORMAT"></a> | ||
### ERR_INVALID_PATH_FORMAT | ||
|
||
Provided path ended with an unexpected character i.e `/` for string and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no need to differentiate between strings and buffers here, it's just about the path ending with /
.
Also we should just make the error code exclusively about "it is a directory" otherwise IMO we should not invent a new error code instead of using ERR_INVALID_ARG_VALUE
. Maybe change this to ERR_PATH_IS_DIRECTORY
(Path %s cannot end with '/' in operations invalid on directories
)?
lib/fs.js
Outdated
@@ -204,22 +204,52 @@ function validateOffsetLengthWrite(offset, length, byteLength) { | |||
} | |||
} | |||
|
|||
function validatePath(path, propName) { | |||
let err; | |||
function validatePath(path, propName, assertFilename, callback) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need to pass a callback here or use handleError
, this kind of errors should be synchronous, just like the type-checking errors (which reminds me the pattern handleError((path = getPathFromURL(path)), callback))
also look weird...why aren't they synchronous? @jasnell ).
This can just be function validatePath(path, propName = 'path', assertFilename = false)
, to hit the assertions you can just go with validatePath(path, 'path', true)
since it does not hurt to explicitly pass in the name of the argument ('path'
), then all the argument-overloading code below would be unnecessary.
lib/fs.js
Outdated
return handle(new errors.Error('ERR_INVALID_PATH_FORMAT', path)); | ||
} | ||
if (isUint8Array(path) && path[path.length - 1] === 47) { | ||
return handle(new errors.Error('ERR_INVALID_PATH_FORMAT', path)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this would be displayed as Path 102,105,108,101,58,47,47,47,116,109,112,47,116,101,115,116,47 ended with an invalid suffix character
, we can either
- Do not display the content in the path if it is a Uint8Array
- Decode it as UTF8 and display it
Personally I prefer 1 even though we currently assume the buffer is encoded in UTF8 in the C++ layer. You can tweak the formatter in internal/errors.js
and use a custom function instead of a string passed to util.format
to do this.
@joyeecheung I think because |
ping @joyeecheung |
Is this not |
@timotew Personally I believe these errors should be thrown synchronously like the type check errors, since they are not caused by any actual system calls and indicate that there is probably a bug in the user's code instead of the user's environment. The user either need to fix the code to trim the trailing |
BTW, since there are plenty of commits in this PR, you might want to squash them on your PR branch before rebasing against master so there will be less conflicts to resolve. |
Ping @timotew |
@joyeecheung , @BridgeAR I'am on it, Thanks. |
89f24cd
to
8dc1c99
Compare
ping @joyeecheung , @BridgeAR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's quite near. Just some nits. 😉
lib/fs.js
Outdated
@@ -81,7 +81,7 @@ function showTruncateDeprecation() { | |||
|
|||
function getOptions(options, defaultOptions) { | |||
if (options === null || options === undefined || | |||
typeof options === 'function') { | |||
typeof options === 'function') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is an unnecessary change. Could you be so kind to revert it?
lib/fs.js
Outdated
// force append behavior when using a supplied file descriptor | ||
if (!options.flag || isFd(path)) | ||
options.flag = 'a'; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There also some unnecessary blank line changes above. Could you revert it?
lib/internal/errors.js
Outdated
@@ -358,6 +358,9 @@ function message(key, args) { | |||
fmt = util.format; | |||
if (args === undefined || args.length === 0) | |||
return msg; | |||
else if (require('internal/util/types').isUint8Array(args[0])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure if this is necessary, this would interfere with other formatters that might want their first arguments formatted differently. Also why are we coercing Uint8Arrays to empty strings? The message Path cannot end with / in operations invalid on directories
looks rather confusing, whereas util.inspect
would print the Uint8Array properly if we just use %s
in the formatter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did this becuase you suggested it.
if (isUint8Array(path) && path[path.length - 1] === 47) {
return handle(new errors.Error('ERR_INVALID_PATH_FORMAT', path));
Note that this would be displayed as Path 102,105,108,101,58,47,47,47,116,109,112,47,116,101,115,116,47 ended with an invalid suffix character, we can either
1.Do not display the content in the path if it is a Uint8Array
2. Decode it as UTF8 and display it
Personally I prefer 1 even though we currently assume the buffer is encoded in UTF8 in the C++ layer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@timotew Sorry, totally forgot about this, I think there is a third option: display the content as util.inspect
displays it...still, come to think of it, Path Uint8Array [1, 2, 3...] cannot end with / in operations invalid on directories
does look confusing as well. So the current message looks fine to me, but we should only change the formatter for this error instead of the message function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there are some APIs missing the error after the rebase? Does the test pass locally?
lib/internal/errors.js
Outdated
@@ -737,6 +740,8 @@ E('ERR_NO_ICU', '%s is not supported on Node.js compiled without ICU'); | |||
E('ERR_NO_LONGER_SUPPORTED', '%s is no longer supported'); | |||
E('ERR_OUT_OF_RANGE', outOfRange); | |||
E('ERR_PARSE_HISTORY_DATA', 'Could not parse history data in %s'); | |||
E('ERR_PATH_IS_DIRECTORY', | |||
'Path %s cannot end with / in operations invalid on directories'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use a custom function here for not displaying Uint8Arrays?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the test pass locally?
Yes. it does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eh, wrong button...did not intend to approve, this still needs some work..but very close!
lib/fs.js
Outdated
@@ -2392,6 +2403,7 @@ ReadStream.prototype.close = function(cb) { | |||
}; | |||
|
|||
fs.createWriteStream = function(path, options) { | |||
path = getPathFromURL(path, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should actually be the wrong spot to test for this. It should be tested in WriteStream
(the same applies to ReadStream
).
lib/fs.js
Outdated
let err; | ||
const isString = typeof path === 'string', | ||
isU8Array = isUint8Array(path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please write things like these as in:
const var1 = 123;
const var2 = 321;
lib/fs.js
Outdated
err = new errors.Error('ERR_PATH_IS_DIRECTORY', path); | ||
if (isU8Array && lastChar === 47) | ||
err = new errors.Error('ERR_PATH_IS_DIRECTORY', path); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking for an UInt8Array is not cheap. Therefore it would be good to rewrite this to:
if (typeof path === 'string') {
err = nullCheck(path, propName, false);
if (assertFilename && err === undefined && path[path.length - 1] === '/')
err = new errors.Errors(...);
} else if (isUint8Array(path)) {
err = nullCheck(path, propName, false);
if (assertFilename && err === undefined && path[path.length - 1] === 47)
err = new errors.Error(...);
} else {
err = new errors.TypeError(...);
}
lib/internal/errors.js
Outdated
@@ -737,6 +737,11 @@ E('ERR_NO_ICU', '%s is not supported on Node.js compiled without ICU'); | |||
E('ERR_NO_LONGER_SUPPORTED', '%s is no longer supported'); | |||
E('ERR_OUT_OF_RANGE', outOfRange); | |||
E('ERR_PARSE_HISTORY_DATA', 'Could not parse history data in %s'); | |||
E('ERR_PATH_IS_DIRECTORY', (path) => { | |||
if (require('internal/util/types').isUint8Array(path)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this error will only be thrown in case it is either a string
or Uint8Array
it would be cheaper to test for the string case.
test/parallel/test-fs-path-err.js
Outdated
urlWithSlash = new URL(stringWithSlash), | ||
urlWithoutSlash = new URL(stringWithoutSlash), | ||
U8ABufferWithSlash = new Uint8Array(Buffer.from(stringWithSlash)), | ||
U8ABufferWithoutSlash = new Uint8Array(Buffer.from(stringWithoutSlash)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above: please write this as:
const var1 = 123;
const var2 = 321;
test({ NODE_DEBUG: '' }, common.mustCall((data) => { | ||
assert(/EISDIR/.test(data)); | ||
assert(!/test-fs-readfile-error/.test(data)); | ||
})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As question: what was the reason that this was removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was to be controlled by rethrow()
but because of Error.captureStackTrace(err, validatePath);
in validatePath()
. This assert(!/test-fs-readfile-error/.test(data));
will always be false.
lib/internal/errors.js
Outdated
@@ -737,6 +737,11 @@ E('ERR_NO_ICU', '%s is not supported on Node.js compiled without ICU'); | |||
E('ERR_NO_LONGER_SUPPORTED', '%s is no longer supported'); | |||
E('ERR_OUT_OF_RANGE', outOfRange); | |||
E('ERR_PARSE_HISTORY_DATA', 'Could not parse history data in %s'); | |||
E('ERR_PATH_IS_DIRECTORY', (path) => { | |||
if (require('internal/util/types').isUint8Array(path)) | |||
path = ''; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sold on showing an empty path. Instead it could just be a placeholder as [Uint8Array path]
.
lib/internal/errors.js
Outdated
E('ERR_PATH_IS_DIRECTORY', (path) => { | ||
if (require('internal/util/types').isUint8Array(path)) | ||
path = ''; | ||
return `Path ${path} cannot end with / in operations invalid on directories`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I try to understand the message but after reading it multiple times, I still can not fully understand it. Can you please rephrase this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It felt that way at first but I left it because English is not my native language and it was suggested by @joyeecheung.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One way to make this more debuggable and consistent with other error messages is:
The argument "${name}" must not end with '/'. Received ${util.inspect(value)}
That requires us to pass in the name of the argument into the formatter, similar to ERR_INVALID_ARG_TYPE
, ERR_OUT_OF_RANGE
and the like.
Ping @timotew |
ping! @BridgeAR , @joyeecheung , @starkwang . |
if (path == null || !path[searchParams] || | ||
!path[searchParams][searchParams]) { | ||
return path; | ||
} | ||
if (path.protocol !== 'file:') | ||
throw new errors.TypeError('ERR_INVALID_URL_SCHEME', 'file'); | ||
|
||
if (assertFilename && path.href[path.href.length - 1] === '/') | ||
throw new errors.Error('ERR_PATH_IS_DIRECTORY', 'path', path.pathname); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The string being checked and reported here should be the result of isWindows ? getPathFromURLWin32(path) : getPathFromURLPosix(path)
for consistency, also because errors like ERR_INVALID_FILE_URL_HOST
should be thrown first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or, maybe getPathFromURL
does not even need to be changed since the path extracted from URLs will be eventually passed to validatePath
anyway..from what I can tell if we update WriteStream
and ReadStream
to validatePath(path, true)
then we don't need to a ssert the file name in getPathFromURL
@@ -2095,6 +2096,7 @@ function WriteStream(path, options) { | |||
if (!(this instanceof WriteStream)) | |||
return new WriteStream(path, options); | |||
|
|||
path = getPathFromURL(path, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is already one this.path = getPathFromURL(path);
below. Also path
is ignored if options.fd !== undefined
so maybe we just need to delete this and change the line below to:
if (options.fd !== undefined) {
this.path = getPathFromURL(path);
validatePath(this.path, true);
}
Same about ReadStream
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
validatePath
only accepts Uint8Array
this will work if Stream path is always Uint8Array
or string
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@timotew The path in the fs stream classes can be falsy if options.fd
is provdided, e.g. fs.createReadStream(null, { fd: fd, encoding: 'utf8' })
is valid. From what I can tell, validatePath
accepts both string
and Uint8Array
.
I ran out of steam on this, if someone else wants to take it over please do, but I'm going to close it for now, kthxbai |
In Windows, if a file name ends with forward slash
/
, fs interprets it as regular file (like there is no slash at all) and does not throw any errors. In Linux it throws error but not in Windows.Fixes: #17801
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
fs