diff --git a/src/shared/__tests__/parse-command.spec.ts b/src/shared/__tests__/parse-command.spec.ts index 5c2a78a210..85e06f2fe6 100644 --- a/src/shared/__tests__/parse-command.spec.ts +++ b/src/shared/__tests__/parse-command.spec.ts @@ -77,6 +77,20 @@ describe("joinQuotedLines", () => { const input = 'bd create "desc\nmore" && npm install\necho done' expect(joinQuotedLines(input)).toEqual(['bd create "desc\nmore" && npm install', "echo done"]) }) + + it('should handle escaped backslash before closing quote (\\\\" sequence)', () => { + // \\" means escaped backslash (\\) followed by closing quote (") + // The string should end at the quote, not continue + const input = 'echo "hello\\\\"\necho done' + expect(joinQuotedLines(input)).toEqual(['echo "hello\\\\"', "echo done"]) + }) + + it('should handle escaped backslash followed by escaped quote (\\\\\\" sequence)', () => { + // \\\" means escaped backslash (\\) followed by escaped quote (\") + // The string should continue past the quote + const input = 'echo "hello\\\\\\"world"\necho done' + expect(joinQuotedLines(input)).toEqual(['echo "hello\\\\\\"world"', "echo done"]) + }) }) describe("parseCommand", () => { diff --git a/src/shared/parse-command.ts b/src/shared/parse-command.ts index de7a3d9583..2d82c5c353 100644 --- a/src/shared/parse-command.ts +++ b/src/shared/parse-command.ts @@ -31,10 +31,20 @@ export function joinQuotedLines(command: string): string[] { while (i < command.length) { const char = command[i] - const prevChar = i > 0 ? command[i - 1] : "" // Handle escape sequences (only in double quotes, single quotes are literal) - const isEscaped = prevChar === "\\" && inDoubleQuote + // Count consecutive backslashes before the current character + // If odd count, the current character is escaped; if even, it's not + // e.g., \" = escaped quote, \\" = escaped backslash + closing quote + let backslashCount = 0 + if (inDoubleQuote) { + let j = i - 1 + while (j >= 0 && command[j] === "\\") { + backslashCount++ + j-- + } + } + const isEscaped = backslashCount % 2 === 1 // Handle quote state changes if (char === '"' && !inSingleQuote && !isEscaped) {