From 4a267e276e95b58f86edadf46993ff215f191cac Mon Sep 17 00:00:00 2001 From: Shrushti Polekar Date: Tue, 22 Jul 2025 18:33:45 +0530 Subject: [PATCH] fix(ui): mentions formatting and AUT failure fix (#22502) * fix mentions formatting * added unit test --- .../ui/src/utils/sanitize.utils.test.ts | 46 +++++++++++++++++++ .../resources/ui/src/utils/sanitize.utils.ts | 22 ++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/sanitize.utils.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/sanitize.utils.test.ts index 3ecaaac4079..ddbe6ea875d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/sanitize.utils.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/sanitize.utils.test.ts @@ -40,4 +40,50 @@ describe('getSanitizeContent', () => { expect(result).toBe(`

abc

`); }); + + describe('HTML Encoding Prevention', () => { + it('should NOT encode entity links with HTML entities', () => { + const input = '<#E::team::Accounting|@Accounting>'; + const result = getSanitizeContent(input); + + // Should NOT contain HTML encoded entities + expect(result).not.toContain('<'); + expect(result).not.toContain('>'); + expect(result).not.toContain('&'); + + // Should contain the original entity link format + expect(result).toBe('<#E::team::Accounting|@Accounting>'); + }); + + it('should NOT encode multiple entity links with HTML entities', () => { + const input = + 'Hello <#E::team::Accounting|@Accounting> and <#E::user::john.doe|@john.doe>'; + const result = getSanitizeContent(input); + + // Should NOT contain HTML encoded entities + expect(result).not.toContain('<'); + expect(result).not.toContain('>'); + expect(result).not.toContain('&'); + + // Should contain the original entity link format + expect(result).toBe( + 'Hello <#E::team::Accounting|@Accounting> and <#E::user::john.doe|@john.doe>' + ); + }); + + it('should NOT encode entity links even when mixed with HTML content', () => { + const input = + '
Hello
<#E::team::Accounting|@Accounting>World'; + const result = getSanitizeContent(input); + + // Should NOT contain HTML encoded entities for the entity link + expect(result).not.toContain('<#E::team::Accounting|@Accounting>'); + expect(result).not.toContain( + '&lt;#E::team::Accounting|@Accounting&gt;' + ); + + // Should contain the original entity link format + expect(result).toContain('<#E::team::Accounting|@Accounting>'); + }); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/sanitize.utils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/sanitize.utils.ts index d974e32d232..8ed37475d90 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/sanitize.utils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/sanitize.utils.ts @@ -13,5 +13,25 @@ import DOMPurify from 'dompurify'; export const getSanitizeContent = (html: string): string => { - return DOMPurify.sanitize(html); + // First, temporarily replace entity links to protect them from encoding + const entityLinkRegex = /<#E::[^>]+>/g; + const entityLinks: string[] = []; + let entityLinkIndex = 0; + + const protectedHtml = html.replace(entityLinkRegex, (match) => { + entityLinks.push(match); + + return `__ENTITY_LINK_${entityLinkIndex++}__`; + }); + + // Sanitize the content with standard DOMPurify settings + const sanitizedContent = DOMPurify.sanitize(protectedHtml); + + // Restore entity links + let restoredContent = sanitizedContent; + entityLinks.forEach((link, index) => { + restoredContent = restoredContent.replace(`__ENTITY_LINK_${index}__`, link); + }); + + return restoredContent; };