diff --git a/CHANGELOG.md b/CHANGELOG.md index 67ab6974..0afe8963 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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/ diff --git a/src/internal/win/cmd.js b/src/internal/win/cmd.js index bd16dfb0..e6f0c34b 100644 --- a/src/internal/win/cmd.js +++ b/src/internal/win/cmd.js @@ -3,19 +3,7 @@ * @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"); -} +import RegExp from "@ericcornelissen/lregexp"; /** * Returns a function to escape arguments for use in CMD for the given use case. @@ -23,22 +11,38 @@ 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 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(/(?^|])", "g"); + const backslashes = new RegExp('(^|[^\\\\])(\\\\+)("|$)', "g"); + return (arg) => + arg + .replace(controls, "") + .replace(newlines, " ") + .replace(quotes, '""') + .replace(specials, '"^$1"') + .replace(backslashes, "$1$2$2$3"); } /** @@ -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]; } /** @@ -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, ""); } diff --git a/test/fixtures/win.js b/test/fixtures/win.js index 17199ac1..93938c30 100644 --- a/test/fixtures/win.js +++ b/test/fixtures/win.js @@ -1766,6 +1766,10 @@ export const escape = { input: '"a', expected: '\\^"a', }, + { + input: 'a""b', + expected: 'a\\^"\\^"b', + }, ], "double quotes ('\"') + backslashes ('\\')": [ { @@ -4550,6 +4554,14 @@ export const quote = { }, ], "double quotes ('\"') + backslashes ('\\')": [ + { + input: '\\"a', + expected: '"\\\\""a"', + }, + { + input: 'a\\"', + expected: '"a\\\\"""', + }, { input: 'a\\"b', expected: '"a\\\\""b"', @@ -4558,6 +4570,10 @@ export const quote = { input: 'a\\\\"b', expected: '"a\\\\\\\\""b"', }, + { + input: 'a\\\\"b\\\\"c', + expected: '"a\\\\\\\\""b\\\\\\\\""c"', + }, ], "backticks ('`')": [ { @@ -4594,6 +4610,14 @@ export const quote = { input: "^a", expected: '""^^"a"', }, + { + input: "a\\^b", + expected: '"a\\\\"^^"b"', + }, + { + input: "^\\", + expected: '""^^"\\\\"', + }, ], "carets ('^') + double quotes ('\"')": [ { @@ -4644,6 +4668,14 @@ export const quote = { input: "%a", expected: '""^%"a"', }, + { + input: "a\\%b", + expected: '"a\\\\"^%"b"', + }, + { + input: "%\\", + expected: '""^%"\\\\"', + }, ], "percentage signs ('%') + double quotes ('\"')": [ { @@ -4676,6 +4708,14 @@ export const quote = { input: "&a", expected: '""^&"a"', }, + { + input: "a\\&b", + expected: '"a\\\\"^&"b"', + }, + { + input: "&\\", + expected: '""^&"\\\\"', + }, ], "ampersands ('&') + double quotes ('\"')": [ { @@ -4744,6 +4784,14 @@ export const quote = { input: "|a", expected: '""^|"a"', }, + { + input: "a\\|b", + expected: '"a\\\\"^|"b"', + }, + { + input: "|\\", + expected: '""^|"\\\\"', + }, ], "pipes ('|') + double quotes ('\"')": [ { @@ -4796,6 +4844,22 @@ export const quote = { input: "ac", expected: '"a"^<"b"^>"c"', }, + { + input: "a\\b", + expected: '"a\\\\"^>"b"', + }, + { + input: "<\\", + expected: '""^<"\\\\"', + }, + { + input: ">\\", + expected: '""^>"\\\\"', + }, ], "angle brackets ('<', '>') + double quotes ('\"')": [ {