Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { render } from '@testing-library/react';
import {
sanitizeHtml,
isProbablyHTML,
Expand All @@ -24,6 +26,7 @@ import {
removeHTMLTags,
isJsonString,
getParagraphContents,
escapeHtml,
} from './html';

describe('sanitizeHtml', () => {
Expand Down Expand Up @@ -65,23 +68,48 @@ describe('sanitizeHtmlIfNeeded', () => {
});
});

describe('escapeHtml', () => {
test('should escape HTML special characters', () => {
const htmlString = '<div>test</div>';
const escaped = escapeHtml(htmlString);
expect(escaped).toBe('&lt;div&gt;test&lt;&#x2F;div&gt;');
});

test('should escape all special characters', () => {
const testString = '<>&"\'/';
const escaped = escapeHtml(testString);
expect(escaped).toBe('&lt;&gt;&amp;&quot;&#x27;&#x2F;');
});

test('should not escape regular text', () => {
const plainText = 'Just a plain text';
const escaped = escapeHtml(plainText);
expect(escaped).toBe(plainText);
});
});

describe('safeHtmlSpan', () => {
test('should return a safe HTML span when the input is HTML', () => {
test('should render HTML content as a span element to display as text', () => {
const htmlString = '<div>Some <b>HTML</b> content</div>';
const safeSpan = safeHtmlSpan(htmlString);
expect(safeSpan).toEqual(
<span
className="safe-html-wrapper"
dangerouslySetInnerHTML={{ __html: htmlString }}
/>,
);
const result = safeHtmlSpan(htmlString);
const { container } = render(result as React.ReactElement);
// The span should contain the raw HTML string as text content
expect(container.textContent).toBe(htmlString);
});

test('should return the input string as is when it is not HTML', () => {
const plainText = 'Just a plain text';
const result = safeHtmlSpan(plainText);
expect(result).toEqual(plainText);
});

test('should preserve HTML tags as visible text', () => {
const htmlString = '<div>test</div>';
const result = safeHtmlSpan(htmlString);
const { container } = render(result as React.ReactElement);
// Should display <div>test</div> as text
expect(container.textContent).toBe(htmlString);
});
});

describe('removeHTMLTags', () => {
Expand Down
27 changes: 20 additions & 7 deletions superset-frontend/packages/superset-ui-core/src/utils/html.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { FilterXSS, getDefaultWhiteList } from 'xss';

const xssFilter = new FilterXSS({
Expand Down Expand Up @@ -70,15 +71,27 @@ export function sanitizeHtmlIfNeeded(htmlString: string) {
return isProbablyHTML(htmlString) ? sanitizeHtml(htmlString) : htmlString;
}

export function safeHtmlSpan(possiblyHtmlString: string) {
/**
* Escapes HTML special characters to display them as text
* Converts < to &lt;, > to &gt;, & to &amp;, etc.
*/
export function escapeHtml(text: string): string {
const map: Record<string, string> = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'/': '&#x2F;',
};
return text.replace(/[&<>"'/]/g, char => map[char]);
}

export function safeHtmlSpan(possiblyHtmlString: string): string | JSX.Element {
const isHtml = isProbablyHTML(possiblyHtmlString);
if (isHtml) {
return (
<span
className="safe-html-wrapper"
dangerouslySetInnerHTML={{ __html: sanitizeHtml(possiblyHtmlString) }}
/>
);
// Render as plain text to display the raw HTML string without interpreting it
return <span style={{ whiteSpace: 'pre-wrap' }}>{possiblyHtmlString}</span>;
}
return possiblyHtmlString;
}
Expand Down