Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions config/eslint.js
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,12 @@ export default [
"grouped-accessor-pairs": ["error"],
"guard-for-in": ["error"],
"id-denylist": ["error"],
"id-length": ["error"],
"id-length": [
"error",
{
exceptions: ["_"],
},
],
"id-match": ["error"],
"init-declarations": ["off"],
"logical-assignment-operators": ["error"],
Expand All @@ -647,7 +652,12 @@ export default [
"max-lines": ["off"],
"max-lines-per-function": ["off"],
"max-nested-callbacks": ["error"],
"max-params": ["error"],
"max-params": [
"error",
{
max: 4,
},
],
"max-statements": ["off"],
"new-cap": ["error"],
"no-alert": ["error"],
Expand Down
124 changes: 68 additions & 56 deletions src/internal/win/powershell.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,7 @@
* @license MPL-2.0
*/

/**
* Escape an argument for use in PowerShell.
*
* @param {string} arg The argument to escape.
* @returns {string} The escaped argument.
*/
function escapeArg(arg) {
arg = arg
.replace(/[\0\u0008\r\u001B\u009B]/gu, "")
.replace(/\n/gu, " ")
.replace(/`/gu, "``")
.replace(/(?<=^|[\s\u0085])([*1-6]?)(>)/gu, "$1`$2")
.replace(/(?<=^|[\s\u0085])([#\-:<@\]])/gu, "`$1")
.replace(/([$&'(),;{|}‘’‚‛“”„])/gu, "`$1");

if (/[\s\u0085]/u.test(arg.replace(/^[\s\u0085]+/gu, ""))) {
arg = arg
.replace(/(?<!\\)(\\*)"/gu, '$1$1`"`"')
.replace(/(?<!\\)(\\+)$/gu, "$1$1");
} else {
arg = arg.replace(/(?<!\\)(\\*)"/gu, '$1$1\\`"');
}

arg = arg.replace(/([\s\u0085])/gu, "`$1");

return arg;
}
import RegExp from "@ericcornelissen/lregexp";

/**
* Returns a function to escape arguments for use in PowerShell for the given
Expand All @@ -38,30 +12,78 @@ function escapeArg(arg) {
* @returns {function(string): string} A function to escape arguments.
*/
export function getEscapeFunction() {
return escapeArg;
const controls = new RegExp("[\0\u0008\r\u001B\u009B]", "g");
const newlines = new RegExp("\n", "g");
const backticks = new RegExp("`", "g");
const redirects = new RegExp("(^|[\\s\u0085])([*1-6]?)(>)", "g");
const specials1 = new RegExp("(^|[\\s\u0085])([#\\-:<@\\]])", "g");
const specials2 = new RegExp("([$&'(),;{|}‘’‚‛“”„])", "g");

const whitespace = new RegExp("([\\s\u0085])", "g");
const whitespacePrefix = new RegExp("^[\\s\u0085]+");

const quote = new RegExp('"', "g");
const backslashBeforeQuote = new RegExp("(^|[^\\\\])(\\\\*)\0", "g");

const backslashSuffix = new RegExp("([^\\\\])(\\\\+)$");

return (arg) => {
arg = arg
.replace(controls, "")
.replace(newlines, " ")
.replace(backticks, "``")
.replace(redirects, "$1$2`$3")
.replace(specials1, "$1`$2")
.replace(specials2, "`$1");

if (whitespace.test(arg.replace(whitespacePrefix, ""))) {
arg = arg
.replace(quote, '\0`"`"')
.replace(backslashBeforeQuote, "$1$2$2")
.replace(backslashSuffix, "$1$2$2");
} else {
arg = arg
.replace(quote, '\0\\`"')
.replace(backslashBeforeQuote, "$1$2$2");
}

arg = arg.replace(whitespace, "`$1");

return arg;
};
}

/**
* Escape an argument for use in PowerShell when the argument is being quoted.
*
* @param {string} arg The argument to escape.
* @returns {string} The escaped argument.
* @returns {function(string): string} A function to escape arguments.
*/
function escapeArgForQuoted(arg) {
arg = arg
.replace(/[\0\u0008\u001B\u009B]/gu, "")
.replace(/\r(?!\n)/gu, "")
.replace(/(['‘’‚‛])/gu, "$1$1");
function getQuoteEscapeFunction() {
const controls = new RegExp("[\0\u0008\u001B\u009B]", "g");
const crs = new RegExp("(\r\n)|\r", "g");
const quotes = new RegExp("(['‘’‚‛])", "g");

if (/[\s\u0085]/u.test(arg)) {
arg = arg
.replace(/(?<!\\)(\\*)"/gu, '$1$1""')
.replace(/(?<!\\)(\\+)$/gu, "$1$1");
} else {
arg = arg.replace(/(?<!\\)(\\*)"/gu, '$1$1\\"');
}
const whitespace = new RegExp("[\\s\u0085]");

const quote = new RegExp('"', "g");
const backslashBeforeQuote = new RegExp("(^|[^\\\\])(\\\\*)\0", "g");

const backslashSuffix = new RegExp("([^\\\\])(\\\\+)$");

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

return arg;
if (whitespace.test(arg)) {
arg = arg
.replace(quote, '\0""')
.replace(backslashBeforeQuote, "$1$2$2")
.replace(backslashSuffix, "$1$2$2");
} else {
arg = arg.replace(quote, '\0\\"').replace(backslashBeforeQuote, "$1$2$2");
}

return arg;
};
}

/**
Expand All @@ -81,18 +103,7 @@ function quoteArg(arg) {
* @returns {(function(string): string)[]} A function pair to escape & quote arguments.
*/
export function getQuoteFunction() {
return [escapeArgForQuoted, quoteArg];
}

/**
* Remove any prefix from the provided argument that might be interpreted as a
* flag on Windows systems for PowerShell.
*
* @param {string} arg The argument to update.
* @returns {string} The updated argument.
*/
function stripFlagPrefix(arg) {
return arg.replace(/^(?:`?-+|\/+)/gu, "");
return [getQuoteEscapeFunction(), quoteArg];
}

/**
Expand All @@ -101,5 +112,6 @@ function stripFlagPrefix(arg) {
* @returns {function(string): string} A function to protect against flag injection.
*/
export function getFlagProtectionFunction() {
return stripFlagPrefix;
const leadingHyphensAndSlashes = new RegExp("^(?:`?-+|\\/+)");
return (arg) => arg.replace(leadingHyphensAndSlashes, "");
}
100 changes: 100 additions & 0 deletions test/fixtures/win.js
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,10 @@ export const escape = {
input: '"a',
expected: '"a',
},
{
input: 'a""b',
expected: 'a""b',
},
],
"backticks ('`')": [
{
Expand Down Expand Up @@ -2984,12 +2988,24 @@ export const escape = {
input: '"a',
expected: '\\`"a',
},
{
input: 'a""b',
expected: 'a\\`"\\`"b',
},
{
input: 'a b""c',
expected: 'a` b`"`"`"`"c',
},
],
"double quotes ('\"') + backslashes ('\\')": [
{
input: 'a\\"b',
expected: 'a\\\\\\`"b',
},
{
input: 'a\\\\"b',
expected: 'a\\\\\\\\\\`"b',
},
{
input: 'a\\"b\\"c',
expected: 'a\\\\\\`"b\\\\\\`"c',
Expand All @@ -3002,6 +3018,10 @@ export const escape = {
input: '\\"a',
expected: '\\\\\\`"a',
},
{
input: '"\\"',
expected: '\\`"\\\\\\`"',
},
],
"double quotes ('\"') + whitespace": [
{
Expand Down Expand Up @@ -3074,6 +3094,10 @@ export const escape = {
input: "a\u0085@b",
expected: "a`\u0085`@b",
},
{
input: "a @b @c",
expected: "a` `@b` `@c",
},
],
"hashtags ('#')": [
{
Expand Down Expand Up @@ -3110,6 +3134,10 @@ export const escape = {
input: "a\u0085#b",
expected: "a`\u0085`#b",
},
{
input: "a #b #c",
expected: "a` `#b` `#c",
},
],
"carets ('^')": [
{
Expand Down Expand Up @@ -3218,6 +3246,10 @@ export const escape = {
input: "a\u0085-b",
expected: "a`\u0085`-b",
},
{
input: "a -b -c",
expected: "a` `-b` `-c",
},
],
"backslashes ('\\')": [
{
Expand All @@ -3236,6 +3268,10 @@ export const escape = {
input: "a\\",
expected: "a\\",
},
{
input: "\\",
expected: "\\",
},
],
"backslashes ('\\') + whitespace": [
{
Expand Down Expand Up @@ -3274,6 +3310,14 @@ export const escape = {
input: " a b\\",
expected: "` ` a` b\\\\",
},
{
input: " \\",
expected: "` \\",
},
{
input: "\\ \\",
expected: "\\` \\\\",
},
],
"colons (':')": [
{
Expand Down Expand Up @@ -3310,6 +3354,10 @@ export const escape = {
input: "a\u0085:b",
expected: "a`\u0085`:b",
},
{
input: "a :b :c",
expected: "a` `:b` `:c",
},
],
"semicolons (';')": [
{
Expand Down Expand Up @@ -3466,6 +3514,18 @@ export const escape = {
input: "a\u0085]b",
expected: "a`\u0085`]b",
},
{
input: "a [b [c",
expected: "a` [b` [c",
},
{
input: "a ]b ]c",
expected: "a` `]b` `]c",
},
{
input: "a [b ]c",
expected: "a` [b` `]c",
},
],
"curly brackets ('{', '}')": [
{
Expand Down Expand Up @@ -3568,6 +3628,14 @@ export const escape = {
input: "a\u0085>b",
expected: "a`\u0085`>b",
},
{
input: "a <b <c",
expected: "a` `<b` `<c",
},
{
input: "a >b >c",
expected: "a` `>b` `>c",
},
],
"right angle brackets ('>') + digits (/[0-9]/)": [
{
Expand Down Expand Up @@ -3650,6 +3718,10 @@ export const escape = {
input: "a 0>b",
expected: "a` 0>b",
},
{
input: "a 0>b 1>c 2>d 3>e 4>f 5>g 6>h 7>i 8>j 9>k",
expected: "a` 0>b` 1`>c` 2`>d` 3`>e` 4`>f` 5`>g` 6`>h` 7>i` 8>j` 9>k",
},
{
input: "a\t1>b",
expected: "a`\t1`>b",
Expand Down Expand Up @@ -3748,6 +3820,10 @@ export const escape = {
input: "a\u0085*>b",
expected: "a`\u0085*`>b",
},
{
input: "a *>b *>c",
expected: "a` *`>b` *`>c",
},
],
"left double quotation mark ('“')": [
{
Expand Down Expand Up @@ -5218,6 +5294,14 @@ export const quote = {
input: '"a',
expected: "'\\\"a'",
},
{
input: 'a""b',
expected: "'a\\\"\\\"b'",
},
{
input: 'a b""c',
expected: '\'a b""""c\'',
},
],
"double quotes ('\"') + backslashes ('\\')": [
{
Expand All @@ -5228,6 +5312,22 @@ export const quote = {
input: 'a\\\\"b',
expected: "'a\\\\\\\\\\\"b'",
},
{
input: 'a\\"b\\"c',
expected: "'a\\\\\\\"b\\\\\\\"c'",
},
{
input: 'a\\"',
expected: "'a\\\\\\\"'",
},
{
input: '\\"a',
expected: "'\\\\\\\"a'",
},
{
input: '"\\"',
expected: "'\\\"\\\\\\\"'",
},
],
"double quotes ('\"') + whitespace": [
{
Expand Down
Loading