From d5ec9ea387faa131e0b82fa247dfc33ab4b26dd7 Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Tue, 28 May 2024 17:53:23 +0100 Subject: [PATCH 1/2] fix: shell escaping on non windows platforms --- bin/pact-broker.ts | 4 +++- bin/pact-message.ts | 4 +++- bin/pact-mock-service.ts | 4 +++- bin/pact-provider-verifier.ts | 4 +++- bin/pact-stub-service.ts | 4 +++- bin/pact.ts | 4 +++- bin/pactflow.ts | 4 +++- 7 files changed, 21 insertions(+), 7 deletions(-) diff --git a/bin/pact-broker.ts b/bin/pact-broker.ts index 2927c077..87e44a1e 100644 --- a/bin/pact-broker.ts +++ b/bin/pact-broker.ts @@ -3,12 +3,14 @@ import childProcess = require('child_process'); import rubyStandalone from '../src/pact-standalone'; +const isWindows = process.platform === 'win32' +const opts = isWindows ? { shell: true } : {} const { error, status } = childProcess.spawnSync( rubyStandalone.brokerFullPath, process.argv.slice(2), { stdio: 'inherit', - shell: true, + ...opts } ); if (error) throw error; diff --git a/bin/pact-message.ts b/bin/pact-message.ts index aae607ae..ce15f45c 100644 --- a/bin/pact-message.ts +++ b/bin/pact-message.ts @@ -3,12 +3,14 @@ import childProcess = require('child_process'); import rubyStandalone from '../src/pact-standalone'; +const isWindows = process.platform === 'win32' +const opts = isWindows ? { shell: true } : {} const { error, status } = childProcess.spawnSync( rubyStandalone.messageFullPath, process.argv.slice(2), { stdio: 'inherit', - shell: true, + ...opts } ); if (error) throw error; diff --git a/bin/pact-mock-service.ts b/bin/pact-mock-service.ts index f4967f26..293c7cb4 100644 --- a/bin/pact-mock-service.ts +++ b/bin/pact-mock-service.ts @@ -3,12 +3,14 @@ import childProcess = require('child_process'); import rubyStandalone from '../src/pact-standalone'; +const isWindows = process.platform === 'win32' +const opts = isWindows ? { shell: true } : {} const { error, status } = childProcess.spawnSync( rubyStandalone.mockServiceFullPath, process.argv.slice(2), { stdio: 'inherit', - shell: true, + ...opts } ); if (error) throw error; diff --git a/bin/pact-provider-verifier.ts b/bin/pact-provider-verifier.ts index b4152fc6..cdd31d29 100644 --- a/bin/pact-provider-verifier.ts +++ b/bin/pact-provider-verifier.ts @@ -3,12 +3,14 @@ import childProcess = require('child_process'); import rubyStandalone from '../src/pact-standalone'; +const isWindows = process.platform === 'win32' +const opts = isWindows ? { shell: true } : {} const { error, status } = childProcess.spawnSync( rubyStandalone.verifierFullPath, process.argv.slice(2), { stdio: 'inherit', - shell: true, + ...opts } ); if (error) throw error; diff --git a/bin/pact-stub-service.ts b/bin/pact-stub-service.ts index 690a9c49..3d26fe97 100644 --- a/bin/pact-stub-service.ts +++ b/bin/pact-stub-service.ts @@ -3,12 +3,14 @@ import childProcess = require('child_process'); import rubyStandalone from '../src/pact-standalone'; +const isWindows = process.platform === 'win32' +const opts = isWindows ? { shell: true } : {} const { error, status } = childProcess.spawnSync( rubyStandalone.stubFullPath, process.argv.slice(2), { stdio: 'inherit', - shell: true, + ...opts } ); if (error) throw error; diff --git a/bin/pact.ts b/bin/pact.ts index 3c51100e..9fae3332 100644 --- a/bin/pact.ts +++ b/bin/pact.ts @@ -3,12 +3,14 @@ import childProcess = require('child_process'); import rubyStandalone from '../src/pact-standalone'; +const isWindows = process.platform === 'win32' +const opts = isWindows ? { shell: true } : {} const { error, status } = childProcess.spawnSync( rubyStandalone.pactFullPath, process.argv.slice(2), { stdio: 'inherit', - shell: true, + ...opts } ); if (error) throw error; diff --git a/bin/pactflow.ts b/bin/pactflow.ts index afde93a4..584a5bdb 100644 --- a/bin/pactflow.ts +++ b/bin/pactflow.ts @@ -3,12 +3,14 @@ import childProcess = require('child_process'); import rubyStandalone from '../src/pact-standalone'; +const isWindows = process.platform === 'win32' +const opts = isWindows ? { shell: true } : {} const { error, status } = childProcess.spawnSync( rubyStandalone.pactflowFullPath, process.argv.slice(2), { stdio: 'inherit', - shell: true, + ...opts } ); if (error) throw error; From 2b0700f782a181ba6e39596285e2a9871ddf0398 Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Tue, 28 May 2024 19:23:01 +0100 Subject: [PATCH 2/2] fix: refactor shell escaping, only on windows and escape cmd/pwsh args --- bin/pact-broker.ts | 16 ++++++++----- bin/pact-message.ts | 17 ++++++++----- bin/pact-mock-service.ts | 17 ++++++++----- bin/pact-provider-verifier.ts | 17 ++++++++----- bin/pact-stub-service.ts | 17 ++++++++----- bin/pact.ts | 17 ++++++++----- bin/pactflow.ts | 17 ++++++++----- src/pact-standalone.ts | 45 +++++++++++++++++++++++++++++++++++ 8 files changed, 121 insertions(+), 42 deletions(-) diff --git a/bin/pact-broker.ts b/bin/pact-broker.ts index 87e44a1e..6c614acd 100644 --- a/bin/pact-broker.ts +++ b/bin/pact-broker.ts @@ -1,16 +1,20 @@ #!/usr/bin/env node import childProcess = require('child_process'); -import rubyStandalone from '../src/pact-standalone'; +import { + standalone, + standaloneUseShell, + setStandaloneArgs, +} from '../src/pact-standalone'; -const isWindows = process.platform === 'win32' -const opts = isWindows ? { shell: true } : {} +const args = process.argv.slice(2); +const opts = standaloneUseShell ? { shell: true } : {}; const { error, status } = childProcess.spawnSync( - rubyStandalone.brokerFullPath, - process.argv.slice(2), + standalone().brokerFullPath, + setStandaloneArgs(args, standaloneUseShell), { stdio: 'inherit', - ...opts + ...opts, } ); if (error) throw error; diff --git a/bin/pact-message.ts b/bin/pact-message.ts index ce15f45c..d1d2214c 100644 --- a/bin/pact-message.ts +++ b/bin/pact-message.ts @@ -1,16 +1,21 @@ #!/usr/bin/env node import childProcess = require('child_process'); -import rubyStandalone from '../src/pact-standalone'; +import { + standalone, + standaloneUseShell, + setStandaloneArgs, +} from '../src/pact-standalone'; + +const args = process.argv.slice(2); +const opts = standaloneUseShell ? { shell: true } : {}; -const isWindows = process.platform === 'win32' -const opts = isWindows ? { shell: true } : {} const { error, status } = childProcess.spawnSync( - rubyStandalone.messageFullPath, - process.argv.slice(2), + standalone().messageFullPath, + setStandaloneArgs(args, standaloneUseShell), { stdio: 'inherit', - ...opts + ...opts, } ); if (error) throw error; diff --git a/bin/pact-mock-service.ts b/bin/pact-mock-service.ts index 293c7cb4..b3542900 100644 --- a/bin/pact-mock-service.ts +++ b/bin/pact-mock-service.ts @@ -1,16 +1,21 @@ #!/usr/bin/env node import childProcess = require('child_process'); -import rubyStandalone from '../src/pact-standalone'; +import { + standalone, + standaloneUseShell, + setStandaloneArgs, +} from '../src/pact-standalone'; + +const args = process.argv.slice(2); +const opts = standaloneUseShell ? { shell: true } : {}; -const isWindows = process.platform === 'win32' -const opts = isWindows ? { shell: true } : {} const { error, status } = childProcess.spawnSync( - rubyStandalone.mockServiceFullPath, - process.argv.slice(2), + standalone().mockServiceFullPath, + setStandaloneArgs(args, standaloneUseShell), { stdio: 'inherit', - ...opts + ...opts, } ); if (error) throw error; diff --git a/bin/pact-provider-verifier.ts b/bin/pact-provider-verifier.ts index cdd31d29..706fc925 100644 --- a/bin/pact-provider-verifier.ts +++ b/bin/pact-provider-verifier.ts @@ -1,16 +1,21 @@ #!/usr/bin/env node import childProcess = require('child_process'); -import rubyStandalone from '../src/pact-standalone'; +import { + standalone, + standaloneUseShell, + setStandaloneArgs, +} from '../src/pact-standalone'; + +const args = process.argv.slice(2); +const opts = standaloneUseShell ? { shell: true } : {}; -const isWindows = process.platform === 'win32' -const opts = isWindows ? { shell: true } : {} const { error, status } = childProcess.spawnSync( - rubyStandalone.verifierFullPath, - process.argv.slice(2), + standalone().verifierFullPath, + setStandaloneArgs(args, standaloneUseShell), { stdio: 'inherit', - ...opts + ...opts, } ); if (error) throw error; diff --git a/bin/pact-stub-service.ts b/bin/pact-stub-service.ts index 3d26fe97..3e02e29c 100644 --- a/bin/pact-stub-service.ts +++ b/bin/pact-stub-service.ts @@ -1,16 +1,21 @@ #!/usr/bin/env node import childProcess = require('child_process'); -import rubyStandalone from '../src/pact-standalone'; +import { + standalone, + standaloneUseShell, + setStandaloneArgs, +} from '../src/pact-standalone'; + +const args = process.argv.slice(2); +const opts = standaloneUseShell ? { shell: true } : {}; -const isWindows = process.platform === 'win32' -const opts = isWindows ? { shell: true } : {} const { error, status } = childProcess.spawnSync( - rubyStandalone.stubFullPath, - process.argv.slice(2), + standalone().stubFullPath, + setStandaloneArgs(args, standaloneUseShell), { stdio: 'inherit', - ...opts + ...opts, } ); if (error) throw error; diff --git a/bin/pact.ts b/bin/pact.ts index 9fae3332..d4afad30 100644 --- a/bin/pact.ts +++ b/bin/pact.ts @@ -1,16 +1,21 @@ #!/usr/bin/env node import childProcess = require('child_process'); -import rubyStandalone from '../src/pact-standalone'; +import { + standalone, + standaloneUseShell, + setStandaloneArgs, +} from '../src/pact-standalone'; + +const args = process.argv.slice(2); +const opts = standaloneUseShell ? { shell: true } : {}; -const isWindows = process.platform === 'win32' -const opts = isWindows ? { shell: true } : {} const { error, status } = childProcess.spawnSync( - rubyStandalone.pactFullPath, - process.argv.slice(2), + standalone().pactFullPath, + setStandaloneArgs(args, standaloneUseShell), { stdio: 'inherit', - ...opts + ...opts, } ); if (error) throw error; diff --git a/bin/pactflow.ts b/bin/pactflow.ts index 584a5bdb..fddc26fd 100644 --- a/bin/pactflow.ts +++ b/bin/pactflow.ts @@ -1,16 +1,21 @@ #!/usr/bin/env node import childProcess = require('child_process'); -import rubyStandalone from '../src/pact-standalone'; +import { + standalone, + standaloneUseShell, + setStandaloneArgs, +} from '../src/pact-standalone'; + +const args = process.argv.slice(2); +const opts = standaloneUseShell ? { shell: true } : {}; -const isWindows = process.platform === 'win32' -const opts = isWindows ? { shell: true } : {} const { error, status } = childProcess.spawnSync( - rubyStandalone.pactflowFullPath, - process.argv.slice(2), + standalone().pactflowPath, + setStandaloneArgs(args, standaloneUseShell), { stdio: 'inherit', - ...opts + ...opts, } ); if (error) throw error; diff --git a/src/pact-standalone.ts b/src/pact-standalone.ts index 94a0ccf8..3dc11632 100644 --- a/src/pact-standalone.ts +++ b/src/pact-standalone.ts @@ -67,4 +67,49 @@ export const standalone = ( }; }; +const isWindows = process.platform === 'win32'; + +function quoteCmdArg(arg: string) { + return `"${arg.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`; +} + +function quotePwshArg(arg: string) { + return `'${arg.replace(/'/g, "''")}'`; +} + +function quotePosixShArg(arg: string) { + return `'${arg.replace(/'/g, "'\\''")}'`; +} + +function testWindowsExe(cmd: string, file: string) { + return new RegExp(`^(?:.*\\\\)?${cmd}(?:\\.exe)?$`, 'i').test(file); +} + +function parseArgs(unparsed_args: string[]) { + if (isWindows === true) { + const file = process.env['comspec'] || 'cmd.exe'; + if (testWindowsExe('cmd', file) === true) { + return unparsed_args.map((i) => quoteCmdArg(i)); + } + if (testWindowsExe('(powershell|pwsh)', file) || file.endsWith('/pwsh')) { + return unparsed_args.map((i) => quotePwshArg(i)); + } + return unparsed_args; + } + return unparsed_args.map((i) => quotePosixShArg(i)); +} + +export function setStandaloneArgs( + unparsed_args: string[], + shell: boolean +): string[] { + let parsedArgs = unparsed_args; + if (shell === true) { + parsedArgs = parseArgs(unparsed_args); + } + return parsedArgs; +} + +export const standaloneUseShell = isWindows; + export default standalone();