Skip to content
Merged
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
3 changes: 2 additions & 1 deletion apps/example-web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ const LINK_REGEX =
/^(?:enriched:\/\/\S+|(?:https?:\/\/)?(?:www\.)?swmansion\.com(?:\/\S*)?)$/i;

const SANITIZATION_CONFIG = {
linkRegex: LINK_REGEX,
linkRegex:
/^(?:enriched:\/\/\S+|(?:https?:\/\/)?(?:www\.)?swmansion\.com(?:\/\S*)?|https?:\/\/\S+)$/i,
};
Comment thread
hejsztynx marked this conversation as resolved.

function App() {
Expand Down
2 changes: 1 addition & 1 deletion apps/example-web/src/components/TextRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { WEB_DEFAULT_HTML_STYLE } from '../defaultHtmlStyle';
import { EnrichedTextActions } from './EnrichedTextActions';

const LINK_REGEX =
/^(?:enriched:\/\/\S+|(?:https?:\/\/)?(?:www\.)?swmansion\.com(?:\/\S*)?)$/i;
/^(?:enriched:\/\/\S+|(?:https?:\/\/)?(?:www\.)?swmansion\.com(?:\/\S*)?|https?:\/\/\S+)$/i;

const SANITIZATION_CONFIG = {
linkRegex: LINK_REGEX,
Expand Down
4 changes: 3 additions & 1 deletion src/web/EnrichedText.css
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,9 @@
vertical-align: text-bottom;
}

.et-view img.error {
.et-view img.error,
.et-view img:not([src]),
.et-view img[src=""] {
content: linear-gradient(transparent, transparent);
background-color: currentColor;
-webkit-mask: var(--et-broken-image-glyph) no-repeat center / contain;
Expand Down
51 changes: 51 additions & 0 deletions src/web/__tests__/sanitization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,57 @@ describe('sanitizeHtmlMention', () => {
});
});

describe('sanitizeHtml <img>', () => {
const urlOnlyRegex = /^(?:enriched:\/\/\S+|https?:\/\/\S+)$/i;

it('keeps src, width, and height with the default config', () => {
const out = sanitizeHtml(
'<img src="https://example.com/a.png" width="80" height="60">'
);
expect(out).toContain('src="https://example.com/a.png"');
expect(out).toContain('width="80"');
expect(out).toContain('height="60"');
});

it('keeps width and height even when a URL-only linkRegex is supplied', () => {
const out = sanitizeHtml(
'<img src="https://example.com/a.png" width="80" height="60" alt="cat">',
{ linkRegex: urlOnlyRegex }
);
expect(out).toContain('width="80"');
expect(out).toContain('height="60"');
expect(out).toContain('src="https://example.com/a.png"');
expect(out).toContain('alt="cat"');
});

it('still validates the img src protocol against the custom linkRegex', () => {
const out = sanitizeHtml(
'<img src="ftp://example.com/a.png" width="80" height="60">',
{ linkRegex: urlOnlyRegex }
);
expect(out).not.toContain('ftp://');
expect(out).toContain('width="80"');
expect(out).toContain('height="60"');
});

it('strips a javascript: src', () => {
const out = sanitizeHtml(
'<img src="javascript:alert(1)" width="80" height="60">',
{ linkRegex: urlOnlyRegex }
);
// eslint-disable-next-line no-script-url
expect(out).not.toContain('javascript:');
});

it('strips event handlers from img', () => {
const out = sanitizeHtml(
'<img src="https://example.com/a.png" onerror="alert(1)" width="80">'
);
expect(out).not.toContain('onerror');
expect(out).toContain('width="80"');
});
});

describe('sanitizeLinkAttributes', () => {
it('strips javascript: URLs from links', () => {
const out = sanitizeHtml('<a href="javascript:alert(1)">x</a>');
Expand Down
7 changes: 6 additions & 1 deletion src/web/sanitization/htmlSanitizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ import type { SanitizationConfig } from '../../types';

const MENTION_ATTRS = ['text', 'indicator'];

// Non-URL <img> attributes we emit. They must be listed as "URI safe" because
// DOMPurify validates every attribute value that isn't in its built-in
// URI_SAFE_ATTRIBUTES set against ALLOWED_URI_REGEXP.
const IMG_DIMENSION_ATTRS = ['width', 'height'];

// Attributes DOMPurify keeps by default and are commonly used, so we don't emit an unnecessary warning
const COMMONLY_ALLOWED_ATTRS = ['id', 'class', 'style'];

export function sanitizeHtml(html: string, config?: SanitizationConfig) {
return DOMPurify.sanitize(html, {
ADD_TAGS: ['mention', 'codeblock'],
ADD_ATTR: MENTION_ATTRS,
ADD_URI_SAFE_ATTR: MENTION_ATTRS,
ADD_URI_SAFE_ATTR: [...MENTION_ATTRS, ...IMG_DIMENSION_ATTRS],
// if not supplied, fall back to DOMPurify's built-in default.
...(config?.linkRegex ? { ALLOWED_URI_REGEXP: config.linkRegex } : {}),
});
Expand Down
Loading