fix(core): Handle every escaped closing bracket in an expression (#40487)

This commit is contained in:
Rup Sarmah 2026-10-13 12:58:10 +00:00 committed by GitHub
parent f090f53480
commit b09efb706c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 40 additions and 4 deletions

View File

@ -17,7 +17,7 @@ const OPEN_BRACKET = /(?<brackets>\{\{)/;
const CLOSE_BRACKET = /(?<brackets>\}\})/;
export const escapeCode = (text: string): string => {
return text.replace('\\}}', '}}');
return text.replace(/\\\}\}/g, '}}');
};
const normalizeBackslashes = (text: string): string => {
@ -99,7 +99,7 @@ export const splitExpression = (expression: string): ExpressionChunk[] => {
// Expressions only have closing brackets escaped
const escapeTmplExpression = (part: string) => {
return part.replace('}}', '\\}}');
return part.replace(/\}\}/g, '\\}}');
};
export const joinExpression = (parts: ExpressionChunk[]): string => {

View File

@ -14,4 +14,8 @@ describe('Expression', () => {
evaluator.execute('{{ import("").then(fs => fs.writeFileSync("/tmp/flag", "flag")) }}', {}),
).toThrow('Imports are not supported');
});
test('Should unescape every escaped closing bracket', () => {
expect(evaluator.execute('{{ [{ a: { b: 1 \\}}, { a: { b: 2 \\}}][1].a.b }}', {})).toBe(2);
});
});

View File

@ -43,6 +43,13 @@ describe('tmpl Expression Parser', () => {
]);
});
test('Multiple escaped closing brackets', () => {
expect(splitExpression('{{ [{ a: { b: 1 \\}}, { a: { b: 2 \\}}] }}')).toEqual([
{ type: 'text', text: '' },
{ type: 'code', text: ' [{ a: { b: 1 }}, { a: { b: 2 }}] ', hasClosingBrackets: true },
]);
});
test('Escaped backslashes before double opening curly braces', () => {
const expr =
'C:\\\\Users\\\\Administrator\\\\Desktop\\\\abc\\\\{{ $json.files[0].fileName }}';
@ -92,5 +99,14 @@ describe('tmpl Expression Parser', () => {
'test {{ code.test("\\}}") }}',
);
});
test('Multiple escaped closing brackets', () => {
expect(
joinExpression([
{ type: 'text', text: '' },
{ type: 'code', text: ' [{ a: { b: 1 }}, { a: { b: 2 }}] ', hasClosingBrackets: true },
]),
).toEqual('{{ [{ a: { b: 1 \\}}, { a: { b: 2 \\}}] }}');
});
});
});

View File

@ -18,7 +18,7 @@ const OPEN_BRACKET = /(?<escape>\\|)(?<brackets>\{\{)/;
const CLOSE_BRACKET = /(?<escape>\\|)(?<brackets>\}\})/;
export const escapeCode = (text: string): string => {
return text.replace('\\}}', '}}');
return text.replace(/\\\}\}/g, '}}');
};
export const splitExpression = (expression: string): ExpressionChunk[] => {
@ -91,7 +91,7 @@ export const splitExpression = (expression: string): ExpressionChunk[] => {
// Expressions only have closing brackets escaped
const escapeTmplExpression = (part: string) => {
return part.replace('}}', '\\}}');
return part.replace(/\}\}/g, '\\}}');
};
export const joinExpression = (parts: ExpressionChunk[]): string => {

View File

@ -72,6 +72,13 @@ describe('Expression Parser', () => {
]);
});
test('Multiple escaped closing brackets', () => {
expect(splitExpression('{{ [{ a: { b: 1 \\}}, { a: { b: 2 \\}}] }}')).toEqual([
{ type: 'text', text: '' },
{ type: 'code', text: ' [{ a: { b: 1 }}, { a: { b: 2 }}] ', hasClosingBrackets: true },
]);
});
test('Empty input (CAT-3075)', () => {
expect(splitExpression('')).toEqual([{ type: 'text', text: '' }]);
});
@ -107,6 +114,15 @@ describe('Expression Parser', () => {
'test {{ code.test("\\}}") }}',
);
});
test('Multiple escaped closing brackets', () => {
expect(
joinExpression([
{ type: 'text', text: '' },
{ type: 'code', text: ' [{ a: { b: 1 }}, { a: { b: 2 }}] ', hasClosingBrackets: true },
]),
).toEqual('{{ [{ a: { b: 1 \\}}, { a: { b: 2 \\}}] }}');
});
});
describe('Edge cases', () => {