Skip to content

Commit 7d99990

Browse files
committed
shellIntegrationAddon: fix incorrect deserializeMessage implementation
The original implementation confused escaped and un-escaped sequences due to squashing each adjacent pair of backslashes prior to parsing. Unlike encoding, decoding cannot be performed in passes this way: it must be sequential, because of the potential for overlapping patterns (e.g. the third backslash in "\\\x3b"). This replaces the implementation with a single sequential regex, which matches the escape sequences and replaces each match. This enables the tests that were broken in the previous implementation. To illustrate, given the following two different original values (here as literals between «», without any escaping): a. «Packing\Stuff\x3BEarmuffs» b. «Packing\Stuff;Earmuffs» Those should be distinctly encoded as: a. «Packing\\Stuff\\x3BEarmuffs» b. «Packing\\Stuff\x3BEarmuffs» The original implementation wrongly threw away the escaping information by replacing each adjacent pairs of backslashes with a single backslash, regardless of whether escaping was in effect or not: a. «Packing\Stuff\x3BEarmuffs» b. «Packing\Stuff\x3BEarmuffs» The new implementation matches, in (a), each "\\"; and there are no non-overlapping "\x…" sequences. In (b), the first "\\" is matches, and the "\x3B" is matched. This works for any correct combination of adjacent escape squences.
1 parent 46bae7f commit 7d99990

File tree

2 files changed

+7
-23
lines changed

2 files changed

+7
-23
lines changed

src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -513,16 +513,12 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
513513
}
514514

515515
export function deserializeMessage(message: string): string {
516-
let result = message.replace(/\\\\/g, '\\');
517-
const deserializeRegex = /\\x([0-9a-f]{2})/i;
518-
while (true) {
519-
const match = result.match(deserializeRegex);
520-
if (match?.index == null || match.length < 2) {
521-
break;
522-
}
523-
result = result.slice(0, match.index) + String.fromCharCode(parseInt(match[1], 16)) + result.slice(match.index + 4);
524-
}
525-
return result;
516+
return message.replaceAll(
517+
// Backslash ('\') followed by an escape operator: either another '\', or 'x' and two hex chars.
518+
/\\(\\|x([0-9a-f]{2}))/gi,
519+
// If it's a hex value, parse it to a character.
520+
// Otherwise the operator is '\', which we return literally, now unescaped.
521+
(_match: string, op: string, hex?: string) => hex ? String.fromCharCode(parseInt(hex, 16)) : op);
526522
}
527523

528524
export function parseKeyValueAssignment(message: string): { key: string; value: string | undefined } {

src/vs/workbench/contrib/terminal/test/browser/xterm/shellIntegrationAddon.test.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -287,20 +287,8 @@ suite('deserializeMessage', () => {
287287
['non-initial escaped backslash followed by literal "x0a" is not a newline', `foo${Backslash}${Backslash}x0a`, `foo${Backslash}x0a`],
288288
];
289289

290-
const BROKEN: readonly string[] = [
291-
'escaped backslash followed by literal "x3b" is not a semicolon',
292-
'non-initial escaped backslash followed by literal "x3b" is not a semicolon',
293-
'escaped backslash followed by literal "x0a" is not a newline',
294-
'non-initial escaped backslash followed by literal "x0a" is not a newline',
295-
];
296-
297290
cases.forEach(([title, input, expected]) => {
298-
const fn = () => strictEqual(deserializeMessage(input), expected);
299-
if (BROKEN.includes(title)) {
300-
test.skip(title, fn);
301-
} else {
302-
test(title, fn);
303-
}
291+
test(title, () => strictEqual(deserializeMessage(input), expected));
304292
});
305293
});
306294

0 commit comments

Comments
 (0)