diff --git a/src/shellEscape.ts b/src/shellEscape.ts index 249f2535..1a4d0ba3 100644 --- a/src/shellEscape.ts +++ b/src/shellEscape.ts @@ -4,27 +4,31 @@ export function shellEscape(args: Array): string { var output: Array = []; if (getOSType() === OSType.windows) { - args.forEach(function(arg) { + args.forEach(function (arg) { // Check if the argument is a file path const isFilePath = /^([a-zA-Z]:)?(\\[^<>:"/\\|?*]+)+\.exe$/.test(arg); if (!isFilePath && /[^A-Za-z0-9_\/:=-]/.test(arg)) { + arg = arg.replace(/\\/g, '\\\\'); arg = '"' + arg.replace(/"/g, '\\"') + '"'; - arg = arg.replace(/^(?:"")+/g, '') // unduplicate double-quote at the beginning + arg = arg + .replace(/^(?:"")+/g, '') // unduplicate double-quote at the beginning .replace(/\\"""/g, '\\"'); // remove non-escaped double-quote if there are enclosed between 2 escaped } output.push(arg); }); return output.join(' '); } else { - args.forEach(function(arg) { + args.forEach(function (arg) { if (/[^A-Za-z0-9_\/:=-]/.test(arg)) { - arg = "'" + arg.replace(/'/g,"'\\''") + "'"; - arg = arg.replace(/^(?:'')+/g, '') // unduplicate single-quote at the beginning + arg = arg.replace(/\\/g, '\\\\'); + arg = "'" + arg.replace(/'/g, "'\\''") + "'"; + arg = arg + .replace(/^(?:'')+/g, '') // unduplicate single-quote at the beginning .replace(/\\'''/g, "\\'"); // remove non-escaped single-quote if there are enclosed between 2 escaped - }; + } output.push(arg); }); return output.join(' '); - }; -}; + } +} diff --git a/test/suite/shellEscape.test.ts b/test/suite/shellEscape.test.ts index 15297c6f..0e8ee0b1 100644 --- a/test/suite/shellEscape.test.ts +++ b/test/suite/shellEscape.test.ts @@ -1,9 +1,8 @@ /* eslint-disable quotes */ import * as assert from 'assert'; -import * as shellEscape from '../../src/shellEscape'; import * as sinon from 'sinon'; import * as utils from '../../src/utils'; - +import {shellEscape} from '../../src/shellEscape'; suite('shellEscape', () => { let sandbox: sinon.SinonSandbox; @@ -19,43 +18,53 @@ suite('shellEscape', () => { suite('shellEscape', () => { test('non windows case: flag with spaces', () => { sandbox.stub(utils, 'getOSType').returns(utils.OSType.macOSarm); - const output = shellEscape.shellEscape(['--project-name', 'test | whoami']); // test | whoami + const output = shellEscape(['--project-name', 'test | whoami']); // test | whoami assert.strictEqual(output, `--project-name 'test | whoami'`); // --project-name 'test | whoami' }); test('non windows case: flag with single quote around entire arg', () => { sandbox.stub(utils, 'getOSType').returns(utils.OSType.macOSarm); - const output = shellEscape.shellEscape(['--project-name', `'test name'`]); // 'test name' + const output = shellEscape(['--project-name', `'test name'`]); // 'test name' assert.strictEqual(output, `--project-name \\''test name'\\'`); // --project-name \''test name'\' }); test('non windows case: flag with double quote', () => { sandbox.stub(utils, 'getOSType').returns(utils.OSType.macOSarm); - const output = shellEscape.shellEscape(['--project-name', `test "name"`]); // test "name" + const output = shellEscape(['--project-name', `test "name"`]); // test "name" assert.strictEqual(output, `--project-name 'test "name"'`); // --project-name 'test "name"' }); test('non windows case: flag with lots of quotes', () => { sandbox.stub(utils, 'getOSType').returns(utils.OSType.macOSarm); - const output = shellEscape.shellEscape(['--project-name', `'test's "name"'`]); // 'test's "name"' + const output = shellEscape(['--project-name', `'test's "name"'`]); // 'test's "name"' assert.strictEqual(output, `--project-name \\''test'\\''s "name"'\\'`); // --project-name \''test'\''s "name"'\' }); + test.only('non windows case: flag with backspace character', () => { + sandbox.stub(utils, 'getOSType').returns(utils.OSType.macOSarm); + const output = shellEscape(['--project-name', `\\bte\\bst | whoami`]); + assert.strictEqual(output, `--project-name '\\\\bte\\\\bst | whoami'`); + }); test.only('windows case: flag with space', () => { sandbox.stub(utils, 'getOSType').returns(utils.OSType.windows); - const output = shellEscape.shellEscape(['--project-name', 'test | whoami']); // test | whoami + const output = shellEscape(['--project-name', 'test | whoami']); // test | whoami assert.strictEqual(output, `--project-name "test | whoami"`); // --project-name "test | whoami" }); test.only('windows case: flag with single quote around entire arg', () => { sandbox.stub(utils, 'getOSType').returns(utils.OSType.windows); - const output = shellEscape.shellEscape(['--project-name', `'test name'`]); // 'test name' + const output = shellEscape(['--project-name', `'test name'`]); // 'test name' assert.strictEqual(output, `--project-name "'test name'"`); // --project-name "'test name'" }); test.only('windows case: flag with double quote', () => { sandbox.stub(utils, 'getOSType').returns(utils.OSType.windows); - const output = shellEscape.shellEscape(['--project-name', `test "name"`]); // test "name" + const output = shellEscape(['--project-name', `test "name"`]); // test "name" assert.strictEqual(output, `--project-name "test \\"name\\""`); // --project-name "test \"name\"" }); test.only('windows case: flag with lots of quotes', () => { sandbox.stub(utils, 'getOSType').returns(utils.OSType.windows); - const output = shellEscape.shellEscape(['--project-name', `'test's "name"'`]); // 'test's "name"' + const output = shellEscape(['--project-name', `'test's "name"'`]); // 'test's "name"' assert.strictEqual(output, `--project-name "'test's \\"name\\"'"`); // --project-name "'test's \"name\"'" }); + test.only('windows case: flag with backspace character', () => { + sandbox.stub(utils, 'getOSType').returns(utils.OSType.windows); + const output = shellEscape(['--project-name', `\\bte\\bst | whoami`]); + assert.strictEqual(output, `--project-name "\\\\bte\\\\bst | whoami"`); + }); }); });