| 'use strict'; |
| |
| // Node.js on Windows should not be able to spawn batch files directly, |
| // only when the 'shell' option is set. An undocumented feature of the |
| // Win32 CreateProcess API allows spawning .bat and .cmd files directly |
| // but it does not sanitize arguments. We cannot do that automatically |
| // because it's sometimes impossible to escape arguments unambiguously. |
| // |
| // Expectation: spawn() and spawnSync() raise EINVAL if and only if: |
| // |
| // 1. 'shell' option is unset |
| // 2. Platform is Windows |
| // 3. Filename ends in .bat or .cmd, case-insensitive |
| // |
| // exec() and execSync() are unchanged. |
| |
| const common = require('../common'); |
| const cp = require('child_process'); |
| const assert = require('assert'); |
| const { isWindows } = common; |
| |
| const expectedCode = isWindows ? 'EINVAL' : 'ENOENT'; |
| const expectedStatus = isWindows ? 1 : 127; |
| |
| const suffixes = |
| 'BAT|bAT|BaT|baT|BAt|bAt|Bat|bat|CMD|cMD|CmD|cmD|CMd|cMd|Cmd|cmd|cmd |cmd .|cmd ....' |
| .split('|'); |
| |
| function testExec(filename) { |
| return new Promise((resolve) => { |
| cp.exec(filename).once('exit', common.mustCall(function(status) { |
| assert.strictEqual(status, expectedStatus); |
| resolve(); |
| })); |
| }); |
| } |
| |
| function testExecSync(filename) { |
| let e; |
| try { |
| cp.execSync(filename); |
| } catch (_e) { |
| e = _e; |
| } |
| if (!e) throw new Error(`Exception expected for ${filename}`); |
| assert.strictEqual(e.status, expectedStatus); |
| } |
| |
| function testSpawn(filename, code) { |
| // Batch file case is a synchronous error, file-not-found is asynchronous. |
| if (code === 'EINVAL') { |
| let e; |
| try { |
| cp.spawn(filename); |
| } catch (_e) { |
| e = _e; |
| } |
| if (!e) throw new Error(`Exception expected for ${filename}`); |
| assert.strictEqual(e.code, code); |
| } else { |
| return new Promise((resolve) => { |
| cp.spawn(filename).once('error', common.mustCall(function(e) { |
| assert.strictEqual(e.code, code); |
| resolve(); |
| })); |
| }); |
| } |
| } |
| |
| function testSpawnSync(filename, code) { |
| { |
| const r = cp.spawnSync(filename); |
| assert.strictEqual(r.error.code, code); |
| } |
| { |
| const r = cp.spawnSync(filename, { shell: true }); |
| assert.strictEqual(r.status, expectedStatus); |
| } |
| } |
| |
| testExecSync('./nosuchdir/nosuchfile'); |
| testSpawnSync('./nosuchdir/nosuchfile', 'ENOENT'); |
| for (const suffix of suffixes) { |
| testExecSync(`./nosuchdir/nosuchfile.${suffix}`); |
| testSpawnSync(`./nosuchdir/nosuchfile.${suffix}`, expectedCode); |
| } |
| |
| go().catch((ex) => { throw ex; }); |
| |
| async function go() { |
| await testExec('./nosuchdir/nosuchfile'); |
| await testSpawn('./nosuchdir/nosuchfile', 'ENOENT'); |
| for (const suffix of suffixes) { |
| await testExec(`./nosuchdir/nosuchfile.${suffix}`); |
| await testSpawn(`./nosuchdir/nosuchfile.${suffix}`, expectedCode); |
| } |
| } |