mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
AAE-42904 Escape html in evaluated fields and variables for rich-text display widget (#11718)
This commit is contained in:
+2
-2
@@ -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('<img src="x" onerror='alert(1)'>');
|
||||
});
|
||||
|
||||
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('&')
|
||||
.split('<')
|
||||
.join('<')
|
||||
.split('>')
|
||||
.join('>')
|
||||
.split('"')
|
||||
.join('"')
|
||||
.split("'")
|
||||
.join(''');
|
||||
}
|
||||
result = result.replace(match, expressionResult);
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -137,13 +137,15 @@ describe('DisplayRichTextWidgetComponent', () => {
|
||||
});
|
||||
|
||||
it('should sanitize unsafe HTML', async () => {
|
||||
mockRichTextParserService.parse.and.returnValue('<img src="x" onerror="alert(\'XSS\')">');
|
||||
widget.field = mockUnsafeFormField;
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const parsedHtmlEl = debugEl.query(By.css(cssSelector.parsedHTML));
|
||||
expect(parsedHtmlEl.nativeElement.innerHTML.includes('<img src="x" onerror="alert(\'XSS\')">')).toBe(false);
|
||||
expect(parsedHtmlEl.nativeElement.innerHTML.includes('img src="x"')).toBe(true);
|
||||
expect(parsedHtmlEl.nativeElement.innerHTML.includes('onerror')).toBe(false);
|
||||
});
|
||||
|
||||
describe('expression evaluation', () => {
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ export class DisplayRichTextWidgetComponent extends BaseDisplayTextWidgetCompone
|
||||
|
||||
private applyExpressionsToBlocks(value: any): void {
|
||||
for (const block of value.blocks) {
|
||||
block.data.text = this.resolveExpressions(block.data.text);
|
||||
block.data.text = this.resolveExpressions(block.data.text, true);
|
||||
}
|
||||
this.field.value = value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user