Skip to content

Commit 06c8864

Browse files
authored
Merge branch 'main' into fix/android-preserve-inline-styles-during-ime-composition
2 parents 25a3c8f + 0fdac7a commit 06c8864

10 files changed

Lines changed: 84 additions & 7 deletions

File tree

apps/example-web/src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ const LINK_REGEX =
3939
/^(?:enriched:\/\/\S+|(?:https?:\/\/)?(?:www\.)?swmansion\.com(?:\/\S*)?)$/i;
4040

4141
const SANITIZATION_CONFIG = {
42-
linkRegex: LINK_REGEX,
42+
linkRegex:
43+
/^(?:enriched:\/\/\S+|(?:https?:\/\/)?(?:www\.)?swmansion\.com(?:\/\S*)?|https?:\/\/\S+)$/i,
4344
};
4445

4546
function App() {

apps/example-web/src/components/TextRenderer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { WEB_DEFAULT_HTML_STYLE } from '../defaultHtmlStyle';
1212
import { EnrichedTextActions } from './EnrichedTextActions';
1313

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

1717
const SANITIZATION_CONFIG = {
1818
linkRegex: LINK_REGEX,

cpp/parser/GumboNormalizer.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,12 @@ static void emit_attributes(GumboElement *el, const char *tag_name,
491491
if (strcmp(tag_name, "a") == 0) {
492492
emit_one_attr(out, el, "href");
493493
} else if (strcmp(tag_name, "img") == 0) {
494-
emit_one_attr(out, el, "src");
494+
const char *src_val = get_attr(el, "src");
495+
if (src_val && src_val[0]) {
496+
emit_one_attr(out, el, "src");
497+
} else {
498+
buffer_append_str(out, " src=\"\"");
499+
}
495500
emit_one_attr(out, el, "alt");
496501
emit_one_attr(out, el, "width");
497502
emit_one_attr(out, el, "height");

cpp/tests/GumboParserTest.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,11 @@ TEST(GumboParserTest, EnrichedTagRemappings) {
282282
EXPECT_EQ(
283283
GumboParser::normalizeHtml("<img src='x' width='100' height='100' />"),
284284
"<img src=\"x\" width=\"100\" height=\"100\" />");
285+
EXPECT_EQ(GumboParser::normalizeHtml("<img width=\"100\" height=\"100\" />"),
286+
"<img src=\"\" width=\"100\" height=\"100\" />");
287+
EXPECT_EQ(GumboParser::normalizeHtml(
288+
"<img src=\"\" width=\"100\" height=\"100\" />"),
289+
"<img src=\"\" width=\"100\" height=\"100\" />");
285290

286291
// Lists
287292
EXPECT_EQ(GumboParser::normalizeHtml("<ul><li>x</li></ul>"),

ios/htmlParser/HtmlParser.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ + (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml
670670
[styleArr addObject:@([ItalicStyle getType])];
671671
} else if ([tagName isEqualToString:@"img"]) {
672672
NSRegularExpression *srcRegex =
673-
[NSRegularExpression regularExpressionWithPattern:@"src=\"([^\"]+)\""
673+
[NSRegularExpression regularExpressionWithPattern:@"src=\"([^\"]*)\""
674674
options:0
675675
error:nullptr];
676676
NSTextCheckingResult *match =

src/web/EnrichedText.css

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,9 @@
366366
vertical-align: text-bottom;
367367
}
368368

369-
.et-view img.error {
369+
.et-view img.error,
370+
.et-view img:not([src]),
371+
.et-view img[src=""] {
370372
content: linear-gradient(transparent, transparent);
371373
background-color: currentColor;
372374
-webkit-mask: var(--et-broken-image-glyph) no-repeat center / contain;

src/web/__tests__/htmlNormalizer.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,14 @@ describe('htmlNormalizer', () => {
262262
"<img src='x' width='100' height='100' />",
263263
'<img src="x" width="100" height="100" />',
264264
],
265+
[
266+
'<img width="100" height="100" />',
267+
'<img src="" width="100" height="100" />',
268+
],
269+
[
270+
'<img src="" width="100" height="100" />',
271+
'<img src="" width="100" height="100" />',
272+
],
265273

266274
// Lists
267275
['<ul><li>x</li></ul>', '<ul><li>x</li></ul>'],

src/web/__tests__/sanitization.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,57 @@ describe('sanitizeHtmlMention', () => {
9999
});
100100
});
101101

102+
describe('sanitizeHtml <img>', () => {
103+
const urlOnlyRegex = /^(?:enriched:\/\/\S+|https?:\/\/\S+)$/i;
104+
105+
it('keeps src, width, and height with the default config', () => {
106+
const out = sanitizeHtml(
107+
'<img src="https://example.com/a.png" width="80" height="60">'
108+
);
109+
expect(out).toContain('src="https://example.com/a.png"');
110+
expect(out).toContain('width="80"');
111+
expect(out).toContain('height="60"');
112+
});
113+
114+
it('keeps width and height even when a URL-only linkRegex is supplied', () => {
115+
const out = sanitizeHtml(
116+
'<img src="https://example.com/a.png" width="80" height="60" alt="cat">',
117+
{ linkRegex: urlOnlyRegex }
118+
);
119+
expect(out).toContain('width="80"');
120+
expect(out).toContain('height="60"');
121+
expect(out).toContain('src="https://example.com/a.png"');
122+
expect(out).toContain('alt="cat"');
123+
});
124+
125+
it('still validates the img src protocol against the custom linkRegex', () => {
126+
const out = sanitizeHtml(
127+
'<img src="ftp://example.com/a.png" width="80" height="60">',
128+
{ linkRegex: urlOnlyRegex }
129+
);
130+
expect(out).not.toContain('ftp://');
131+
expect(out).toContain('width="80"');
132+
expect(out).toContain('height="60"');
133+
});
134+
135+
it('strips a javascript: src', () => {
136+
const out = sanitizeHtml(
137+
'<img src="javascript:alert(1)" width="80" height="60">',
138+
{ linkRegex: urlOnlyRegex }
139+
);
140+
// eslint-disable-next-line no-script-url
141+
expect(out).not.toContain('javascript:');
142+
});
143+
144+
it('strips event handlers from img', () => {
145+
const out = sanitizeHtml(
146+
'<img src="https://example.com/a.png" onerror="alert(1)" width="80">'
147+
);
148+
expect(out).not.toContain('onerror');
149+
expect(out).toContain('width="80"');
150+
});
151+
});
152+
102153
describe('sanitizeLinkAttributes', () => {
103154
it('strips javascript: URLs from links', () => {
104155
const out = sanitizeHtml('<a href="javascript:alert(1)">x</a>');

src/web/normalization/htmlNormalizer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ function emitAttributes(el: Element, name: string): string {
267267
return emitOneAttr(el, 'href');
268268
case 'img':
269269
return (
270-
emitOneAttr(el, 'src') +
270+
(el.getAttribute('src') ? emitOneAttr(el, 'src') : ' src=""') +
271271
emitOneAttr(el, 'alt') +
272272
emitOneAttr(el, 'width') +
273273
emitOneAttr(el, 'height')

src/web/sanitization/htmlSanitizer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@ import type { SanitizationConfig } from '../../types';
33

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

6+
// Non-URL <img> attributes we emit. They must be listed as "URI safe" because
7+
// DOMPurify validates every attribute value that isn't in its built-in
8+
// URI_SAFE_ATTRIBUTES set against ALLOWED_URI_REGEXP.
9+
const IMG_DIMENSION_ATTRS = ['width', 'height'];
10+
611
// Attributes DOMPurify keeps by default and are commonly used, so we don't emit an unnecessary warning
712
const COMMONLY_ALLOWED_ATTRS = ['id', 'class', 'style'];
813

914
export function sanitizeHtml(html: string, config?: SanitizationConfig) {
1015
return DOMPurify.sanitize(html, {
1116
ADD_TAGS: ['mention', 'codeblock'],
1217
ADD_ATTR: MENTION_ATTRS,
13-
ADD_URI_SAFE_ATTR: MENTION_ATTRS,
18+
ADD_URI_SAFE_ATTR: [...MENTION_ATTRS, ...IMG_DIMENSION_ATTRS],
1419
// if not supplied, fall back to DOMPurify's built-in default.
1520
...(config?.linkRegex ? { ALLOWED_URI_REGEXP: config.linkRegex } : {}),
1621
});

0 commit comments

Comments
 (0)