Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Versioning].
- Add support for `bsd-csh` as a C shell (`csh`) identifier. ([#2388])
- Improve rejecting non-array inputs to `escapeAll` & `quoteAll`. ([#2363],
[#2382])
- Expand support for `--enable-experimental-regexp-engine`. ([#2358])
- Expand support for `--enable-experimental-regexp-engine`. ([#2358], [#2383])

## [2.1.8] - 2026-01-25

Expand Down Expand Up @@ -387,6 +387,7 @@ Versioning].
[#2358]: https://github.com/ericcornelissen/shescape/pull/2358
[#2363]: https://github.com/ericcornelissen/shescape/pull/2363
[#2382]: https://github.com/ericcornelissen/shescape/pull/2382
[#2383]: https://github.com/ericcornelissen/shescape/pull/2383
[#2388]: https://github.com/ericcornelissen/shescape/pull/2388
[552e8ea]: https://github.com/ericcornelissen/shescape/commit/552e8eab56861720b1d4e5474fb65741643358f9
[keep a changelog]: https://keepachangelog.com/en/1.0.0/
Expand Down
66 changes: 30 additions & 36 deletions src/internal/win/cmd.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,46 @@
* @license MPL-2.0
*/

/**
* Escape an argument for use in CMD.
*
* @param {string} arg The argument to escape.
* @returns {string} The escaped argument.
*/
function escapeArg(arg) {
return arg
.replace(/[\0\u0008\r\u001B\u009B]/gu, "")
.replace(/\n/gu, " ")
.replace(/(?<!\\)(\\*)"/gu, '$1$1\\"')
.replace(/(["%&<>^|])/gu, "^$1");
}
import RegExp from "@ericcornelissen/lregexp";

/**
* Returns a function to escape arguments for use in CMD for the given use case.
*
* @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 specials = new RegExp("([%&<>^|])", "g");
const quotes = new RegExp('"', "g");
const backslashes = new RegExp("(^|[^\\\\])(\\\\*)\0", "g");
return (arg) =>
arg
.replace(controls, "")
.replace(newlines, " ")
.replace(specials, "^$1")
.replace(quotes, '\0\\^"')
.replace(backslashes, "$1$2$2");
}

/**
* Escape an argument for use in CMD 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) {
return arg
.replace(/[\0\u0008\r\u001B\u009B]/gu, "")
.replace(/\n/gu, " ")
.replace(/"/gu, '""')
.replace(/([%&<>^|])/gu, '"^$1"')
.replace(/(?<!\\)(\\*)(?="|$)/gu, "$1$1");
function getQuoteEscapeFunction() {
const controls = new RegExp("[\0\u0008\r\u001B\u009B]", "g");
const newlines = new RegExp("\n", "g");
const quotes = new RegExp('"', "g");
const specials = new RegExp("([%&<>^|])", "g");
const backslashes = new RegExp('(^|[^\\\\])(\\\\+)("|$)', "g");
return (arg) =>
arg
.replace(controls, "")
.replace(newlines, " ")
.replace(quotes, '""')
.replace(specials, '"^$1"')
.replace(backslashes, "$1$2$2$3");
}

/**
Expand All @@ -57,18 +61,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 CMD.
*
* @param {string} arg The argument to update.
* @returns {string} The updated argument.
*/
function stripFlagPrefix(arg) {
return arg.replace(/^(?:-+|\/+)/gu, "");
return [getQuoteEscapeFunction(), quoteArg];
}

/**
Expand All @@ -77,5 +70,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, "");
}
64 changes: 64 additions & 0 deletions test/fixtures/win.js
Original file line number Diff line number Diff line change
Expand Up @@ -1766,6 +1766,10 @@ export const escape = {
input: '"a',
expected: '\\^"a',
},
{
input: 'a""b',
expected: 'a\\^"\\^"b',
},
],
"double quotes ('\"') + backslashes ('\\')": [
{
Expand Down Expand Up @@ -4550,6 +4554,14 @@ export const quote = {
},
],
"double quotes ('\"') + backslashes ('\\')": [
{
input: '\\"a',
expected: '"\\\\""a"',
},
{
input: 'a\\"',
expected: '"a\\\\"""',
},
{
input: 'a\\"b',
expected: '"a\\\\""b"',
Expand All @@ -4558,6 +4570,10 @@ export const quote = {
input: 'a\\\\"b',
expected: '"a\\\\\\\\""b"',
},
{
input: 'a\\\\"b\\\\"c',
expected: '"a\\\\\\\\""b\\\\\\\\""c"',
},
],
"backticks ('`')": [
{
Expand Down Expand Up @@ -4594,6 +4610,14 @@ export const quote = {
input: "^a",
expected: '""^^"a"',
},
{
input: "a\\^b",
expected: '"a\\\\"^^"b"',
},
{
input: "^\\",
expected: '""^^"\\\\"',
},
],
"carets ('^') + double quotes ('\"')": [
{
Expand Down Expand Up @@ -4644,6 +4668,14 @@ export const quote = {
input: "%a",
expected: '""^%"a"',
},
{
input: "a\\%b",
expected: '"a\\\\"^%"b"',
},
{
input: "%\\",
expected: '""^%"\\\\"',
},
],
"percentage signs ('%') + double quotes ('\"')": [
{
Expand Down Expand Up @@ -4676,6 +4708,14 @@ export const quote = {
input: "&a",
expected: '""^&"a"',
},
{
input: "a\\&b",
expected: '"a\\\\"^&"b"',
},
{
input: "&\\",
expected: '""^&"\\\\"',
},
],
"ampersands ('&') + double quotes ('\"')": [
{
Expand Down Expand Up @@ -4744,6 +4784,14 @@ export const quote = {
input: "|a",
expected: '""^|"a"',
},
{
input: "a\\|b",
expected: '"a\\\\"^|"b"',
},
{
input: "|\\",
expected: '""^|"\\\\"',
},
],
"pipes ('|') + double quotes ('\"')": [
{
Expand Down Expand Up @@ -4796,6 +4844,22 @@ export const quote = {
input: "a<b>c",
expected: '"a"^<"b"^>"c"',
},
{
input: "a\\<b",
expected: '"a\\\\"^<"b"',
},
{
input: "a\\>b",
expected: '"a\\\\"^>"b"',
},
{
input: "<\\",
expected: '""^<"\\\\"',
},
{
input: ">\\",
expected: '""^>"\\\\"',
},
],
"angle brackets ('<', '>') + double quotes ('\"')": [
{
Expand Down
Loading