diff --git a/index.js b/index.js index 4c657b1..426e8cd 100644 --- a/index.js +++ b/index.js @@ -24,6 +24,21 @@ function isFound(glob) { return isGlob(glob); } +function getSymlinkInfo(filepath, cb) { + fs.realpath(filepath, function (err, realPath) { + if (err) return cb(err); + + fs.lstat(realPath, function (err, lstat) { + if (err) return cb(err); + + cb(null, { + destinationPath: realPath, + destinationStat: lstat, + }); + }); + }); +} + function walkdir() { var readdirOpts = { withFileTypes: true, @@ -114,12 +129,15 @@ function walkdir() { if (dirent.isSymbolicLink()) { // If it's a symlink, check if the symlink points to a directory - fs.stat(nextpath, function (err, stats) { + getSymlinkInfo(nextpath, function (err, info) { if (err) { return cb(err); } - if (stats.isDirectory()) { + if ( + info.destinationStat.isDirectory() && + !nextpath.startsWith(info.destinationPath + path.sep) // don't follow circular symlinks + ) { cb(null, nextpath); } else { cb(); diff --git a/test/fixtures/symlinks/circular-symlink b/test/fixtures/symlinks/circular-symlink new file mode 120000 index 0000000..6581736 --- /dev/null +++ b/test/fixtures/symlinks/circular-symlink @@ -0,0 +1 @@ +../../ \ No newline at end of file diff --git a/test/fixtures/symlinks/folder-a/folder-a-file.txt b/test/fixtures/symlinks/folder-a/folder-a-file.txt new file mode 100644 index 0000000..74614c9 --- /dev/null +++ b/test/fixtures/symlinks/folder-a/folder-a-file.txt @@ -0,0 +1 @@ +hello a diff --git a/test/fixtures/symlinks/folder-a/link-to-b b/test/fixtures/symlinks/folder-a/link-to-b new file mode 120000 index 0000000..50822b2 --- /dev/null +++ b/test/fixtures/symlinks/folder-a/link-to-b @@ -0,0 +1 @@ +../folder-b \ No newline at end of file diff --git a/test/fixtures/symlinks/folder-b/folder-b-file.txt b/test/fixtures/symlinks/folder-b/folder-b-file.txt new file mode 100644 index 0000000..2d684fd --- /dev/null +++ b/test/fixtures/symlinks/folder-b/folder-b-file.txt @@ -0,0 +1 @@ +world b diff --git a/test/fixtures/symlinks/folder-b/link-to-a b/test/fixtures/symlinks/folder-b/link-to-a new file mode 120000 index 0000000..4caf2d9 --- /dev/null +++ b/test/fixtures/symlinks/folder-b/link-to-a @@ -0,0 +1 @@ +../folder-a \ No newline at end of file diff --git a/test/index.js b/test/index.js index 8f7a78e..5325a0e 100644 --- a/test/index.js +++ b/test/index.js @@ -818,12 +818,48 @@ function suite(moduleName) { dir + '/fixtures/symlinks/symlink-dest/hey/isaidhey/whatsgoingon/test.txt', }, + + { + cwd: dir, + base: dir + '/fixtures/symlinks', + path: dir + '/fixtures/symlinks/folder-a/folder-a-file.txt', + }, + { + cwd: dir, + base: dir + '/fixtures/symlinks', + path: dir + '/fixtures/symlinks/folder-b/folder-b-file.txt', + }, + + // It should follow these circular symlinks, but not infinitely + { + cwd: dir, + base: dir + '/fixtures/symlinks', + path: dir + '/fixtures/symlinks/folder-a/link-to-b/folder-b-file.txt', + }, + { + cwd: dir, + base: dir + '/fixtures/symlinks', + path: dir + '/fixtures/symlinks/folder-b/link-to-a/folder-a-file.txt', + }, + + // And it should follow a symlink to a parent directory (circular symlink) without blowing up + { + cwd: dir, + base: dir + '/fixtures/symlinks', + path: + dir + + '/fixtures/symlinks/symlink-dest/hey/isaidhey/whatsgoingon/test.txt', + }, ]; function assert(pathObjs) { - expect(pathObjs.length).toBe(2); + expect(pathObjs.length).toBe(6); expect(pathObjs).toContainEqual(expected[0]); expect(pathObjs).toContainEqual(expected[1]); + expect(pathObjs).toContainEqual(expected[2]); + expect(pathObjs).toContainEqual(expected[3]); + expect(pathObjs).toContainEqual(expected[4]); + expect(pathObjs).toContainEqual(expected[5]); } stream.pipeline(