Skip to content

Commit ec2e2f7

Browse files
Correct escaping of double qouting for PowerShell
As identified by differential testing (see [1]), this fixes escaping of double quoting. In particular for cases like `"\\"` in which the first `"` prevented the regular expression from matching the second `"` due to the first capturing group in `backslashQuote`. This is fixed using the marker technique introduced in [2]. To prevent regressions various unit test fixtures for this and similar cases were added. [1]: cc1076d [2]: 0f38a42
1 parent cc1076d commit ec2e2f7

File tree

3 files changed

+43
-21
lines changed

3 files changed

+43
-21
lines changed

src/internal/win/powershell.js

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ export function getEscapeFunction() {
2222
const whitespace = new RegExp("([\\s\u0085])", "g");
2323
const whitespacePrefix = new RegExp("^[\\s\u0085]+");
2424

25-
const backslashQuote = new RegExp('(^|[^\\\\])(\\\\*)("+)', "g");
25+
const quote = new RegExp('"', "g");
26+
const backslashBeforeQuote = new RegExp("(^|[^\\\\])(\\\\*)\0", "g");
27+
2628
const backslashSuffix = new RegExp("([^\\\\])(\\\\+)$");
2729

2830
return (arg) => {
@@ -36,16 +38,13 @@ export function getEscapeFunction() {
3638

3739
if (whitespace.test(arg.replace(whitespacePrefix, ""))) {
3840
arg = arg
39-
.replace(
40-
backslashQuote,
41-
(_, $1, $2, $3) => `${$1}${$2}${$2}${'`"`"'.repeat($3.length)}`,
42-
)
41+
.replace(quote, '\0`"`"')
42+
.replace(backslashBeforeQuote, "$1$2$2")
4343
.replace(backslashSuffix, "$1$2$2");
4444
} else {
45-
arg = arg.replace(
46-
backslashQuote,
47-
(_, $1, $2, $3) => `${$1}${$2}${$2}${'\\`"'.repeat($3.length)}`,
48-
);
45+
arg = arg
46+
.replace(quote, '\0\\`"')
47+
.replace(backslashBeforeQuote, "$1$2$2");
4948
}
5049

5150
arg = arg.replace(whitespace, "`$1");
@@ -66,24 +65,21 @@ function getQuoteEscapeFunction() {
6665

6766
const whitespace = new RegExp("[\\s\u0085]");
6867

69-
const backslashQuote = new RegExp('(^|[^\\\\])(\\\\*)("+)', "g");
68+
const quote = new RegExp('"', "g");
69+
const backslashBeforeQuote = new RegExp("(^|[^\\\\])(\\\\*)\0", "g");
70+
7071
const backslashSuffix = new RegExp("([^\\\\])(\\\\+)$");
7172

7273
return (arg) => {
7374
arg = arg.replace(controls, "").replace(crs, "$1").replace(quotes, "$1$1");
7475

7576
if (whitespace.test(arg)) {
7677
arg = arg
77-
.replace(
78-
backslashQuote,
79-
(_, $1, $2, $3) => `${$1}${$2}${$2}${'""'.repeat($3.length)}`,
80-
)
78+
.replace(quote, '\0""')
79+
.replace(backslashBeforeQuote, "$1$2$2")
8180
.replace(backslashSuffix, "$1$2$2");
8281
} else {
83-
arg = arg.replace(
84-
backslashQuote,
85-
(_, $1, $2, $3) => `${$1}${$2}${$2}${'\\"'.repeat($3.length)}`,
86-
);
82+
arg = arg.replace(quote, '\0\\"').replace(backslashBeforeQuote, "$1$2$2");
8783
}
8884

8985
return arg;

test/fixtures/win.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3002,6 +3002,10 @@ export const escape = {
30023002
input: 'a\\"b',
30033003
expected: 'a\\\\\\`"b',
30043004
},
3005+
{
3006+
input: 'a\\\\"b',
3007+
expected: 'a\\\\\\\\\\`"b',
3008+
},
30053009
{
30063010
input: 'a\\"b\\"c',
30073011
expected: 'a\\\\\\`"b\\\\\\`"c',
@@ -3014,6 +3018,10 @@ export const escape = {
30143018
input: '\\"a',
30153019
expected: '\\\\\\`"a',
30163020
},
3021+
{
3022+
input: '"\\"',
3023+
expected: '\\`"\\\\\\`"',
3024+
},
30173025
],
30183026
"double quotes ('\"') + whitespace": [
30193027
{
@@ -5304,6 +5312,22 @@ export const quote = {
53045312
input: 'a\\\\"b',
53055313
expected: "'a\\\\\\\\\\\"b'",
53065314
},
5315+
{
5316+
input: 'a\\"b\\"c',
5317+
expected: "'a\\\\\\\"b\\\\\\\"c'",
5318+
},
5319+
{
5320+
input: 'a\\"',
5321+
expected: "'a\\\\\\\"'",
5322+
},
5323+
{
5324+
input: '\\"a',
5325+
expected: "'\\\\\\\"a'",
5326+
},
5327+
{
5328+
input: '"\\"',
5329+
expected: "'\\\"\\\\\\\"'",
5330+
},
53075331
],
53085332
"double quotes ('\"') + whitespace": [
53095333
{

test/unit/win/differential.test.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import * as fc from "fast-check";
1010
import * as old from "../../../node_modules/shescape-previous/src/internal/win/powershell.js";
1111
import * as upd from "../../../src/internal/win/powershell.js";
1212

13+
const numRuns = 5_000_000;
14+
1315
testProp(
1416
"escape functionality is unchanged",
1517
[fc.string()],
@@ -21,7 +23,7 @@ testProp(
2123
const want = oldFn(arg);
2224
t.is(got, want);
2325
},
24-
{ numRuns: 1_000_000 },
26+
{ numRuns },
2527
);
2628

2729
testProp(
@@ -35,7 +37,7 @@ testProp(
3537
const want = oldFn[0](oldFn[1](arg));
3638
t.is(got, want);
3739
},
38-
{ numRuns: 1_000_000 },
40+
{ numRuns },
3941
);
4042

4143
testProp(
@@ -49,5 +51,5 @@ testProp(
4951
const want = oldFn(arg);
5052
t.is(got, want);
5153
},
52-
{ numRuns: 1_000_000 },
54+
{ numRuns },
5355
);

0 commit comments

Comments
 (0)