Skip to content

Commit 8dae55f

Browse files
termosanfischer
authored andcommitted
Fix(which): match only executable files (#874)
On Unix, this only matches files with the exec bit set. On Windows, this only matches files which are readable (since Windows has different rules for execution). Fixes #657.
1 parent 6d66a1a commit 8dae55f

File tree

3 files changed

+48
-5
lines changed

3 files changed

+48
-5
lines changed

src/which.js

+23-4
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,33 @@ common.register('which', _which, {
1313
// set on Windows.
1414
var XP_DEFAULT_PATHEXT = '.com;.exe;.bat;.cmd;.vbs;.vbe;.js;.jse;.wsf;.wsh';
1515

16+
// For earlier versions of NodeJS that doesn't have a list of constants (< v6)
17+
var FILE_EXECUTABLE_MODE = 1;
18+
19+
function isWindowsPlatform() {
20+
return process.platform === 'win32';
21+
}
22+
1623
// Cross-platform method for splitting environment `PATH` variables
1724
function splitPath(p) {
1825
return p ? p.split(path.delimiter) : [];
1926
}
2027

28+
// Tests are running all cases for this func but it stays uncovered by codecov due to unknown reason
29+
/* istanbul ignore next */
30+
function isExecutable(pathName) {
31+
try {
32+
// TODO(node-support): replace with fs.constants.X_OK once remove support for node < v6
33+
fs.accessSync(pathName, FILE_EXECUTABLE_MODE);
34+
} catch (err) {
35+
return false;
36+
}
37+
return true;
38+
}
39+
2140
function checkPath(pathName) {
22-
return fs.existsSync(pathName) && !common.statFollowLinks(pathName).isDirectory();
41+
return fs.existsSync(pathName) && !common.statFollowLinks(pathName).isDirectory()
42+
&& (isWindowsPlatform() || isExecutable(pathName));
2343
}
2444

2545
//@
@@ -37,9 +57,8 @@ function checkPath(pathName) {
3757
function _which(options, cmd) {
3858
if (!cmd) common.error('must specify command');
3959

40-
var isWindows = process.platform === 'win32';
41-
var pathEnv = process.env.path || process.env.Path || process.env.PATH;
42-
var pathArray = splitPath(pathEnv);
60+
var isWindows = isWindowsPlatform();
61+
var pathArray = splitPath(process.env.PATH);
4362

4463
var queryMatches = [];
4564

test/resources/which/node

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
text file, not an executable

test/which.js

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from 'fs';
2+
import path from 'path';
23

34
import test from 'ava';
45

@@ -69,5 +70,27 @@ test('Searching with -a flag returns an array with first item equals to the regu
6970
t.falsy(shell.error());
7071
t.truthy(resultForWhich);
7172
t.truthy(resultForWhichA);
72-
t.is(resultForWhich.toString(), resultForWhichA[0].toString());
73+
t.is(resultForWhich.toString(), resultForWhichA[0]);
74+
});
75+
76+
test('None executable files does not appear in the result list', t => {
77+
const commandName = 'node'; // Should be an existing command
78+
const extraPath = path.resolve(__dirname, 'resources', 'which');
79+
const matchingFile = path.resolve(extraPath, commandName);
80+
const pathEnv = process.env.PATH;
81+
82+
// make sure that file is exists (will throw error otherwise)
83+
t.truthy(fs.existsSync(matchingFile));
84+
85+
process.env.PATH = extraPath + path.delimiter + process.env.PATH;
86+
const resultForWhich = shell.which(commandName);
87+
const resultForWhichA = shell.which('-a', commandName);
88+
t.falsy(shell.error());
89+
t.truthy(resultForWhich);
90+
t.truthy(resultForWhichA);
91+
t.truthy(resultForWhichA.length);
92+
t.not(resultForWhich.toString(), matchingFile);
93+
t.is(resultForWhichA.indexOf(matchingFile), -1);
94+
95+
process.env.PATH = pathEnv;
7396
});

0 commit comments

Comments
 (0)