AAE-42904 Escape html in evaluated fields and variables for rich-text display widget (#11718)

This commit is contained in:
David Olson
2026-03-09 10:25:55 -05:00
committed by GitHub
parent c428f547bc
commit 68b1b98c83
5 changed files with 48 additions and 5 deletions
@@ -68,8 +68,8 @@ export abstract class BaseDisplayTextWidgetComponent extends WidgetComponent imp
protected abstract reevaluateExpressions(): void;
private readonly destroyRef = inject(DestroyRef);
protected resolveExpressions(text: string): string {
return this.formExpressionService.resolveExpressions(this.field.form, text);
protected resolveExpressions(text: string, escapeHtml?: boolean): string {
return this.formExpressionService.resolveExpressions(this.field.form, text, escapeHtml);
}
private applyExpressions() {
@@ -268,6 +268,34 @@ describe('FormExpressionService', () => {
expect(result).toBe('true');
});
describe('when escapeHtml is true', () => {
let getFieldSpy: jasmine.Spy;
beforeEach(() => {
getFieldSpy = spyOn(formModel, 'getFieldById');
});
it('should escape ampersands in resolved field values', () => {
getFieldSpy.and.returnValue({ id: 'f', value: 'a & b' } as any);
expect(service.resolveExpressions(formModel, '${field.f}', true)).toBe('a & b');
});
it('should escape all HTML special characters in an XSS-like payload', () => {
getFieldSpy.and.returnValue({ id: 'f', value: `<img src="x" onerror='alert(1)'>` } as any);
expect(service.resolveExpressions(formModel, '${field.f}', true)).toBe('&lt;img src=&quot;x&quot; onerror=&#039;alert(1)&#039;&gt;');
});
it('should not escape characters when escapeHtml is false', () => {
getFieldSpy.and.returnValue({ id: 'f', value: '<b>bold</b>' } as any);
expect(service.resolveExpressions(formModel, '${field.f}', false)).toBe('<b>bold</b>');
});
it('should not escape characters when escapeHtml is omitted', () => {
getFieldSpy.and.returnValue({ id: 'f', value: '<b>bold</b>' } as any);
expect(service.resolveExpressions(formModel, '${field.f}')).toBe('<b>bold</b>');
});
});
});
describe('getFieldDependencies', () => {
@@ -27,7 +27,7 @@ export class FormExpressionService {
private readonly VARIABLE_PREFIX = 'variable.';
private readonly VARIABLES_REGEX = /(?:field|variable)\.[a-zA-Z_$][a-zA-Z0-9_$]*/g;
resolveExpressions(form: FormModel, formField: string): string {
resolveExpressions(form: FormModel, formField: string, escapeHtml?: boolean): string {
let result = formField || '';
const matches = result.match(this.GLOBAL_EXPRESSION_REGEX);
@@ -43,6 +43,19 @@ export class FormExpressionService {
} else if (typeof expressionResult !== 'string') {
expressionResult = JSON.stringify(expressionResult);
}
if (escapeHtml) {
expressionResult = expressionResult
.split('&')
.join('&amp;')
.split('<')
.join('&lt;')
.split('>')
.join('&gt;')
.split('"')
.join('&quot;')
.split("'")
.join('&#039;');
}
result = result.replace(match, expressionResult);
}