-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
fs,win: fix bug in paths with trailing slashes #54160
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -709,14 +709,30 @@ const validateOffsetLengthWrite = hideStackFrames( | |||
| }, | ||||
| ); | ||||
|
|
||||
| const validatePath = hideStackFrames((path, propName = 'path') => { | ||||
| const validatePath = hideStackFrames((path, propName = 'path', isFile) => { | ||||
| if (typeof path !== 'string' && !isUint8Array(path)) { | ||||
| throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(propName, ['string', 'Buffer', 'URL'], path); | ||||
| } | ||||
|
|
||||
| const pathIsString = typeof path === 'string'; | ||||
| const pathIsUint8Array = isUint8Array(path); | ||||
|
|
||||
| if (isFile) { | ||||
| const lastCharacter = path[path.length - 1]; | ||||
| if ( | ||||
| lastCharacter === '/' || lastCharacter === 47 || | ||||
| (isWindows && (lastCharacter === '\\' || lastCharacter === 92)) | ||||
| ) { | ||||
| throw new ERR_FS_EISDIR({ | ||||
| code: 'ERR_FS_EISDIR', | ||||
| message: 'is a directory', | ||||
| path, | ||||
| syscall: 'validatePath', | ||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not a syscall.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've applied the fix you suggested, but as you can see the CI doesn't accept throwing an error without a syscall. I didn't get this on Windows, only Linux.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the review. Fixed. |
||||
| errno: ERR_FS_EISDIR, | ||||
| }); | ||||
| } | ||||
| } | ||||
|
|
||||
| // We can only perform meaningful checks on strings and Uint8Arrays. | ||||
| if ((!pathIsString && !pathIsUint8Array) || | ||||
| (pathIsString && !StringPrototypeIncludes(path, '\u0000')) || | ||||
|
|
@@ -731,11 +747,12 @@ const validatePath = hideStackFrames((path, propName = 'path') => { | |||
| ); | ||||
| }); | ||||
|
|
||||
| const getValidatedPath = hideStackFrames((fileURLOrPath, propName = 'path') => { | ||||
| const path = toPathIfFileURL(fileURLOrPath); | ||||
| validatePath(path, propName); | ||||
| return path; | ||||
| }); | ||||
| const getValidatedPath = | ||||
|
anonrig marked this conversation as resolved.
|
||||
| hideStackFrames((fileURLOrPath, propName = 'path', isFile = false) => { | ||||
| const path = toPathIfFileURL(fileURLOrPath); | ||||
| validatePath(path, propName, isFile); | ||||
| return path; | ||||
| }); | ||||
|
|
||||
| const getValidatedFd = hideStackFrames((fd, propName = 'fd') => { | ||||
| if (ObjectIs(fd, -0)) { | ||||
|
|
||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| 'use strict'; | ||
|
|
||
| const common = require('../common'); | ||
| const tmpdir = require('../common/tmpdir'); | ||
| const assert = require('assert'); | ||
| const path = require('path'); | ||
| const fs = require('fs'); | ||
| const URL = require('url').URL; | ||
|
|
||
| tmpdir.refresh(); | ||
|
|
||
| let fileCounter = 1; | ||
| const nextFile = () => path.join(tmpdir.path, `file${fileCounter++}`); | ||
|
|
||
| const generateStringPath = (file, suffix = '') => file + suffix; | ||
|
|
||
| const generateURLPath = (file, suffix = '') => | ||
| new URL('file://' + path.resolve(file) + suffix); | ||
|
|
||
| const generateUint8ArrayPath = (file, suffix = '') => | ||
| new Uint8Array(Buffer.from(file + suffix)); | ||
|
|
||
| const generatePaths = (file, suffix = '') => [ | ||
| generateStringPath(file, suffix), | ||
| generateURLPath(file, suffix), | ||
| generateUint8ArrayPath(file, suffix), | ||
| ]; | ||
|
|
||
| const generateNewPaths = (suffix = '') => [ | ||
| generateStringPath(nextFile(), suffix), | ||
| generateURLPath(nextFile(), suffix), | ||
| generateUint8ArrayPath(nextFile(), suffix), | ||
| ]; | ||
|
|
||
| function checkSyncFn(syncFn, p, args, fail) { | ||
| const result = fail ? 'must fail' : 'must succeed'; | ||
| const failMsg = `failed testing sync ${p} ${syncFn.name} ${result}`; | ||
| if (!fail) { | ||
| try { | ||
| syncFn(p, ...args); | ||
| } catch (e) { | ||
| console.log(failMsg, e); | ||
| throw e; | ||
| } | ||
| } else { | ||
| assert.throws(() => syncFn(p, ...args), { | ||
| code: 'ERR_FS_EISDIR', | ||
| }, failMsg); | ||
| } | ||
| } | ||
|
|
||
| function checkAsyncFn(asyncFn, p, args, fail) { | ||
| const result = fail ? 'must fail' : 'must succeed'; | ||
| const failMsg = `failed testing async ${p} ${asyncFn.name} ${result}`; | ||
| if (!fail) { | ||
| try { | ||
| asyncFn(p, ...args, common.mustCall()); | ||
| } catch (e) { | ||
| console.log(failMsg, e); | ||
| throw e; | ||
| } | ||
| } else { | ||
| assert.throws( | ||
| () => asyncFn(p, ...args, common.mustNotCall()), { | ||
| code: 'ERR_FS_EISDIR', | ||
| }, | ||
| failMsg | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| function checkPromiseFn(promiseFn, p, args, fail) { | ||
| const result = fail ? 'must fail' : 'must succeed'; | ||
| const failMsg = `failed testing promise ${p} ${promiseFn.name} ${result}`; | ||
| if (!fail) { | ||
| const r = promiseFn(p, ...args).catch((err) => { | ||
| console.log(failMsg, err); | ||
| throw err; | ||
| }); | ||
| if (r && r.close) r.close(); | ||
| } else { | ||
| assert.rejects( | ||
| promiseFn(p, ...args), { | ||
| code: 'ERR_FS_EISDIR', | ||
| }, | ||
| failMsg | ||
| ).then(common.mustCall()); | ||
| } | ||
| } | ||
|
|
||
| function check(asyncFn, syncFn, promiseFn, | ||
| { suffix, fail, args = [] }) { | ||
| const file = nextFile(); | ||
| fs.writeFile(file, 'data', (e) => { | ||
| assert.ifError(e); | ||
| const paths = generatePaths(file, suffix); | ||
| for (const p of paths) { | ||
| if (asyncFn) checkAsyncFn(asyncFn, p, args, fail); | ||
| if (promiseFn) checkPromiseFn(promiseFn, p, args, fail); | ||
| if (syncFn) checkSyncFn(syncFn, p, args, fail); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| function checkManyToMany(asyncFn, syncFn, promiseFn, | ||
| { suffix, fail, args = [] }) { | ||
| const source = nextFile(); | ||
| fs.writeFile(source, 'data', (err) => { | ||
| assert.ifError(err); | ||
| if (asyncFn) { | ||
| generateNewPaths(suffix) | ||
| .forEach((p) => checkAsyncFn(asyncFn, source, [p, ...args], fail)); | ||
| } | ||
| if (promiseFn) { | ||
| generateNewPaths(suffix) | ||
| .forEach((p) => checkPromiseFn(promiseFn, source, [p, ...args], fail)); | ||
| } | ||
| if (syncFn) { | ||
| generateNewPaths(suffix) | ||
| .forEach((p) => checkSyncFn(syncFn, source, [p, ...args], fail)); | ||
| } | ||
| }); | ||
| } | ||
| check(fs.readFile, fs.readFileSync, fs.promises.readFile, | ||
| { suffix: '', fail: false }); | ||
| check(fs.readFile, fs.readFileSync, fs.promises.readFile, | ||
| { suffix: '/', fail: true }); | ||
| check(fs.writeFile, fs.writeFileSync, fs.promises.writeFile, | ||
| { suffix: '', fail: false, args: ['data'] }); | ||
| check(fs.writeFile, fs.writeFileSync, fs.promises.writeFile, | ||
| { suffix: '/', fail: true, args: ['data'] }); | ||
| check(fs.appendFile, fs.appendFileSync, fs.promises.appendFile, | ||
| { suffix: '', fail: false, args: ['data'] }); | ||
| check(fs.appendFile, fs.appendFileSync, fs.promises.appendFile, | ||
| { suffix: '/', fail: true, args: ['data'] }); | ||
| check(undefined, fs.createReadStream, undefined, | ||
| { suffix: '', fail: false }); | ||
| check(undefined, fs.createReadStream, undefined, | ||
| { suffix: '/', fail: true }); | ||
| check(undefined, fs.createWriteStream, undefined, | ||
| { suffix: '', fail: false }); | ||
| check(undefined, fs.createWriteStream, undefined, | ||
| { suffix: '/', fail: true }); | ||
| check(fs.truncate, fs.truncateSync, fs.promises.truncate, | ||
| { suffix: '', fail: false, args: [42] }); | ||
| check(fs.truncate, fs.truncateSync, fs.promises.truncate, | ||
| { suffix: '/', fail: true, args: [42] }); | ||
|
|
||
| check(fs.open, fs.openSync, fs.promises.open, { suffix: '', fail: false }); | ||
| check(fs.open, fs.openSync, fs.promises.open, { suffix: '/', fail: true }); | ||
|
|
||
| checkManyToMany(fs.copyFile, fs.copyFileSync, fs.promises.copyFile, | ||
| { suffix: '', fail: false }); | ||
|
|
||
| checkManyToMany(fs.copyFile, fs.copyFileSync, fs.promises.copyFile, | ||
| { suffix: '/', fail: true }); | ||
|
|
||
| if (common.isWindows) { | ||
| check(fs.readFile, fs.readFileSync, fs.promises.readFile, | ||
| { suffix: '\\', fail: true }); | ||
| check(fs.writeFile, fs.writeFileSync, fs.promises.writeFile, | ||
| { suffix: '\\', fail: true, args: ['data'] }); | ||
| check(fs.appendFile, fs.appendFileSync, fs.promises.appendFile, | ||
| { suffix: '\\', fail: true, args: ['data'] }); | ||
| check(undefined, fs.createReadStream, undefined, | ||
| { suffix: '\\', fail: true }); | ||
| check(undefined, fs.createWriteStream, undefined, | ||
| { suffix: '\\', fail: true }); | ||
| check(fs.truncate, fs.truncateSync, fs.promises.truncate, | ||
| { suffix: '\\', fail: true, args: [42] }); | ||
| check(fs.open, fs.openSync, fs.promises.open, { suffix: '\\', fail: true }); | ||
| checkManyToMany(fs.copyFile, fs.copyFileSync, fs.promises.copyFile, | ||
| { suffix: '\\', fail: 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.
Can you add a JSDoc to this function and adding a small comment that explains the behavior of this new flag?
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.
Fixed.