diff --git a/lib/make-spawn-args.js b/lib/make-spawn-args.js index 181be84..aa241d5 100644 --- a/lib/make-spawn-args.js +++ b/lib/make-spawn-args.js @@ -3,6 +3,24 @@ const isWindows = require('./is-windows.js') const setPATH = require('./set-path.js') const {resolve} = require('path') const npm_config_node_gyp = require.resolve('node-gyp/bin/node-gyp.js') +const { quoteForShell, ShellString, ShellStringText, ShellStringUnquoted } = require('puka') + +const escapeCmd = cmd => { + const result = [] + const parsed = ShellString.sh([cmd]) + for (const child of parsed.children) { + if (child instanceof ShellStringText) { + const children = child.contents.filter(segment => segment !== null).map(segment => quoteForShell(segment, false, isWindows && 'win32')) + result.push(...children) + } else if (child instanceof ShellStringUnquoted) { + result.push(child.value) + } else { + result.push(isWindows ? '&' : ';') + } + } + + return result.join('') +} const makeSpawnArgs = options => { const { @@ -16,7 +34,7 @@ const makeSpawnArgs = options => { } = options const isCmd = /(?:^|\\)cmd(?:\.exe)?$/i.test(scriptShell) - const args = isCmd ? ['/d', '/s', '/c', `"${cmd}"`] : ['-c', cmd] + const args = isCmd ? ['/d', '/s', '/c', escapeCmd(cmd)] : ['-c', escapeCmd(cmd)] const spawnOpts = { env: setPATH(path, { diff --git a/package-lock.json b/package-lock.json index bc6c44e..39bb163 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@npmcli/promise-spawn": "^1.3.0", "infer-owner": "^1.0.4", "node-gyp": "^7.1.0", + "puka": "^1.0.1", "read-package-json-fast": "^1.1.3" }, "devDependencies": { @@ -3319,6 +3320,14 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, + "node_modules/puka": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/puka/-/puka-1.0.1.tgz", + "integrity": "sha512-ssjRZxBd7BT3dte1RR3VoeT2cT/ODH8x+h0rUF1rMqB0srHYf48stSDWfiYakTp5UBZMxroZhB2+ExLDHm7W3g==", + "engines": { + "node": ">=4" + } + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -3981,7 +3990,13 @@ "bundleDependencies": [ "ink", "treport", - "@types/react" + "@types/react", + "import-jsx", + "minipass", + "signal-exit", + "tap-parser", + "tap-yaml", + "yaml" ], "dev": true, "dependencies": { @@ -9274,6 +9289,11 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, + "puka": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/puka/-/puka-1.0.1.tgz", + "integrity": "sha512-ssjRZxBd7BT3dte1RR3VoeT2cT/ODH8x+h0rUF1rMqB0srHYf48stSDWfiYakTp5UBZMxroZhB2+ExLDHm7W3g==" + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", diff --git a/package.json b/package.json index c8a052f..609bc1b 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@npmcli/promise-spawn": "^1.3.0", "infer-owner": "^1.0.4", "node-gyp": "^7.1.0", + "puka": "^1.0.1", "read-package-json-fast": "^1.1.3" }, "files": [ diff --git a/test/make-spawn-args.js b/test/make-spawn-args.js index 1584a18..c957280 100644 --- a/test/make-spawn-args.js +++ b/test/make-spawn-args.js @@ -21,10 +21,10 @@ if (isWindows) { t.match(makeSpawnArgs({ event: 'event', path: 'path', - cmd: 'script', + cmd: 'script "quoted parameter"; second command', }), [ 'cmd', - [ '/d', '/s', '/c', '"script"' ], + [ '/d', '/s', '/c', `script "quoted parameter"& second command` ], { env: { npm_package_json: /package\.json$/, @@ -43,10 +43,10 @@ if (isWindows) { t.match(makeSpawnArgs({ event: 'event', path: 'path', - cmd: 'script', + cmd: 'script "quoted parameter"; second command', }), [ 'blrorp', - [ '-c', 'script' ], + [ '-c', `script "quoted parameter"& second command` ], { env: { npm_package_json: /package\.json$/, @@ -62,11 +62,11 @@ if (isWindows) { t.match(makeSpawnArgs({ event: 'event', path: 'path', - cmd: 'script', + cmd: 'script "quoted parameter"; second command', scriptShell: 'cmd.exe', }), [ 'cmd.exe', - [ '/d', '/s', '/c', '"script"' ], + [ '/d', '/s', '/c', `script "quoted parameter"& second command` ], { env: { npm_package_json: /package\.json$/, @@ -88,10 +88,10 @@ if (isWindows) { t.match(makeSpawnArgs({ event: 'event', path: 'path', - cmd: 'script', + cmd: 'script "quoted parameter"; second command', }), [ 'sh', - [ '-c', 'script' ], + [ '-c', `script 'quoted parameter'; second command` ], { env: { npm_package_json: /package\.json$/, @@ -109,11 +109,11 @@ if (isWindows) { t.match(makeSpawnArgs({ event: 'event', path: 'path', - cmd: 'script', + cmd: 'script "quoted parameter"; second command', scriptShell: 'cmd.exe', }), [ 'cmd.exe', - [ '/d', '/s', '/c', '"script"' ], + [ '/d', '/s', '/c', `script 'quoted parameter'; second command` ], { env: { npm_package_json: /package\.json$/,