fix(mu): email content not displayed due to sanitize html

(cherry picked from commit 2f06e52d81864a62d6e588bb24a6845e20371bf4)
This commit is contained in:
dab246
2025-12-15 14:32:02 +07:00
committed by Dat H. Pham
parent 50671b98ee
commit 6f455cc728
10 changed files with 362 additions and 141 deletions
@@ -1,12 +1,13 @@
import 'package:core/presentation/utils/html_transformer/text/standardize_html_sanitizing_transformers.dart';
import 'package:flutter_test/flutter_test.dart';
import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:core/presentation/utils/html_transformer/text/standardize_html_sanitizing_transformers.dart';
void main() {
group('StandardizeHtmlSanitizingTransformers.process', () {
const transformer = StandardizeHtmlSanitizingTransformers();
const htmlEscape = HtmlEscape();
const listHTMLTags = [
const allowedTags = <String>[
'div',
'span',
'p',
@@ -16,12 +17,10 @@ void main() {
'font',
'u',
'center',
'style',
'section',
'google-sheets-html-origin',
'supress_time_adjustment',
];
const listOnEventAttributes = [
const eventAttributes = <String>[
'mousedown',
'mouseenter',
'mouseleave',
@@ -80,124 +79,368 @@ void main() {
'touchcancel',
];
test('SHOULD remove all `on*` attributes tag', () {
for (var i = 0; i < listOnEventAttributes.length; i++) {
final inputHtml = '<img src="1" href="1" on${listOnEventAttributes[i]}="javascript:alert(1)">';
final result = transformer.process(inputHtml, htmlEscape);
String sanitize(String input) =>
transformer.process(input, htmlEscape).trim();
expect(result, equals('<img src="1">'));
}
});
test('SHOULD remove all `on*` attributes for any tags', () {
for (var tag in listHTMLTags) {
for (var event in listOnEventAttributes) {
final inputHtml = '<$tag on$event="javascript:alert(1)"></$tag>';
final result = transformer.process(inputHtml, htmlEscape);
expect(result, equals('<$tag></$tag>'));
group('Event attributes, script, iframe fully sanitized', () {
test('SHOULD remove all on* event attributes from IMG WHEN event attributes exist', () {
for (final evt in eventAttributes) {
final html = '<img src="1" href="1" on$evt="javascript:alert(1)">';
expect(sanitize(html), equals('<img src="1">'));
}
}
});
test('SHOULD remove all on* event attributes FROM any allowed tag FOR all events', () {
for (final tag in allowedTags) {
for (final evt in eventAttributes) {
final html = '<$tag on$evt="javascript:alert(1)"></$tag>';
expect(sanitize(html), equals('<$tag></$tag>'));
}
}
});
test('SHOULD remove all on* event attributes FROM <colgroup>', () {
for (final evt in eventAttributes) {
final html = '<table><colgroup on$evt="javascript:alert(1)"></colgroup></table>';
expect(sanitize(html), equals('<table><colgroup></colgroup></table>'));
}
});
test('SHOULD remove all on* event attributes FROM <col>', () {
for (final evt in eventAttributes) {
final html =
'<table><colgroup><col on$evt="javascript:alert(1)"></colgroup></table>';
expect(sanitize(html),
equals('<table><colgroup><col></colgroup></table>'));
}
});
test('SHOULD remove <script> elements entirely', () {
expect(sanitize('<script>alert("x")</script>'), equals(''));
});
test('SHOULD remove <iframe> elements entirely', () {
expect(sanitize('<iframe onmouseover="prompt(1)"></iframe>'), equals(''));
});
test('SHOULD remove nested <script> elements', () {
const html = '<div><p>Hello<script>alert(1)</script></p></div>';
expect(sanitize(html), equals('<div><p>Hello</p></div>'));
});
});
test('SHOULD remove all `on*` attributes for `colgroup` tag', () {
for (var event in listOnEventAttributes) {
final inputHtml = '<table><colgroup on$event="javascript:alert(1)"></colgroup></table>';
final result = transformer.process(inputHtml, htmlEscape);
group('Href/src sanitization unsafe removed, safe preserved', () {
test('SHOULD remove javascript: href FROM <a>', () {
const html = '<a href="javascript:alert(1)" id="id1">test</a>';
expect(sanitize(html), equals('<a id="id1">test</a>'));
});
expect(result, equals('<table><colgroup></colgroup></table>'));
}
test('SHOULD remove invalid JS event attributes FROM <img>', () {
const html = '<img src="1" href="1" onerror="javascript:alert(1)">';
expect(sanitize(html), equals('<img src="1">'));
});
test('SHOULD keep base64 image sources intact', () {
const html = '<img src="data:image/jpeg;base64,AAAABBBBCCCC==">';
expect(
sanitize(html),
equals('<img src="data:image/jpeg;base64,AAAABBBBCCCC==">'));
});
test('SHOULD keep cid: image sources intact', () {
expect(
sanitize('<img src="cid:email123">'),
equals('<img src="cid:email123">'));
});
});
test('SHOULD remove all `on*` attributes for `col` tag', () {
for (var event in listOnEventAttributes) {
final inputHtml = '<table><colgroup><col on$event="javascript:alert(1)"></colgroup></table>';
final result = transformer.process(inputHtml, htmlEscape);
group('Unknown tags, structural tags unwrap or preserve safely', () {
test('SHOULD preserve nav/main/footer but strip dangerous attributes', () {
expect(sanitize('<nav href="javascript:1"></nav>'), equals('<nav></nav>'));
expect(sanitize('<main href="javascript:1"></main>'), equals('<main></main>'));
expect(sanitize('<footer href="javascript:1"></footer>'), equals('<footer></footer>'));
});
expect(result, equals('<table><colgroup><col></colgroup></table>'));
}
test('SHOULD unwrap <supress_time_adjustment> AND keep children', () {
const html =
'<supress_time_adjustment href="javascript:1"><div><p>Hello</p></div></supress_time_adjustment>';
expect(sanitize(html), equals('<div><p>Hello</p></div>'));
});
test('SHOULD unwrap <google-sheets-html-origin> AND keep children', () {
const html =
'<google-sheets-html-origin href="javascript:1"><div><p>Hello</p></div></google-sheets-html-origin>';
expect(sanitize(html), equals('<div><p>Hello</p></div>'));
});
test('SHOULD unwrap unknown tags AND keep children', () {
expect(sanitize('<custom><p>Hello</p></custom>'), equals('<p>Hello</p>'));
});
test('SHOULD unwrap nested unknown tags AND keep children', () {
expect(
sanitize('<x1><x2><p>Hello</p></x2></x1>'),
equals('<p>Hello</p>'));
});
});
test('SHOULD remove attributes of IMG tag WHEN they are invalid', () {
const inputHtml = '<img src="1" href="1" onerror="javascript:alert(1)">';
final result = transformer.process(inputHtml, htmlEscape);
group('CSS sanitization unsafe styles removed', () {
test('SHOULD keep plain text containing JS-like keywords', () {
const html = 'hello <p>document.cookie</p> world';
expect(result, equals('<img src="1">'));
expect(
sanitize(html),
equals('hello <p>document.cookie</p> world'),
);
});
test('SHOULD remove HTML comments entirely', () {
const html = '<div><!--test--><p>A</p><!--x--></div>';
expect(sanitize(html), equals('<div><p>A</p></div>'));
});
test('SHOULD remove unsafe CSS FROM <style>', () {
const html =
'<style>p{position:absolute;color:red;background:url(javascript:evil);}</style>';
expect(sanitize(html), equals(''));
});
test('SHOULD sanitize inline CSS by removing dangerous properties', () {
const html =
'<p style="color:red;position:absolute;background:url(javascript:evil)">X</p>';
expect(sanitize(html), equals('<p style="color: red">X</p>'));
});
});
test('SHOULD remove all SCRIPTS tags', () {
const inputHtml = '<script>alert("This is an alert message!");</script>';
final result = transformer.process(inputHtml, htmlEscape).trim();
group('ID and class validation valid preserved, invalid removed', () {
test('SHOULD preserve valid id/class values', () {
const html = '<div id="abc123" class="valid_class"></div>';
expect(
sanitize(html),
equals('<div id="abc123" class="valid_class"></div>'));
});
expect(result, equals(''));
test('SHOULD remove invalid id/class values', () {
const html = '<div id="!!!" class="@@@"></div>';
expect(sanitize(html), equals('<div></div>'));
});
});
test('SHOULD remove all IFRAME tags', () {
const inputHtml = '<iframe style="xg-p:absolute;top:0;left:0;width:100%;height:100%" onmouseover="prompt(1)">';
final result = transformer.process(inputHtml, htmlEscape);
group('Complex nested HTML sanitized safely', () {
test('SHOULD remove all dangerous content AND preserve safe nodes', () {
const html = '''
<div onclick="x">
<p>Hello<script>alert(1)</script></p>
<img src="cid:x1" onerror="hack()">
<a href="javascript:evil()" id="ok">link</a>
</div>
''';
expect(result, equals(''));
final out = sanitize(html);
expect(out.contains('javascript'), false);
expect(out.contains('hack'), false);
expect(out.contains('script'), false);
expect(out.contains('evil'), false);
expect(out.contains('onclick'), false);
expect(out.contains('<img'), true);
expect(out.contains('<div'), true);
expect(out.contains('<a'), true);
expect(out.contains('Hello'), true);
});
});
test('SHOULD remove href attribute of A tag WHEN it is invalid', () {
const inputHtml = '<a href="javascript:alert(1)" id="id1">test</a>';
final result = transformer.process(inputHtml, htmlEscape);
group('FORM attributes dangerous removed', () {
final dangerousFormAttributes = [
'action="https://malicious.com/x"',
'action="javascript:1"',
'method="POST"',
'target="_blank"',
'enctype="multipart/form-data"',
'formaction="https://evil.com"',
'formmethod="POST"',
'formtarget="_blank"',
'formenctype="text/plain"',
'autocomplete="off"',
'novalidate',
'accept-charset="UTF-8"',
'name="myForm"',
];
expect(result, equals('<a id="id1">test</a>'));
test('SHOULD remove every dangerous FORM attribute', () {
for (final attr in dangerousFormAttributes) {
final html = '<form $attr></form>';
expect(sanitize(html), equals(''));
}
});
});
test('SHOULD persist value src attribute of IMG tag WHEN it is base64 string', () {
const inputHtml = '<img src="data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA">';
final result = transformer.process(inputHtml, htmlEscape);
group('FORM child elements all removed', () {
final forbiddenChildren = [
'<input type="text">',
'<input type="password">',
'<input type="hidden">',
'<input type="file">',
'<button>Submit</button>',
'<textarea></textarea>',
'<select><option>A</option></select>',
'<option>A</option>',
];
expect(result, equals('<img src="data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA">'));
test('SHOULD remove dangerous FORM child elements', () {
for (final child in forbiddenChildren) {
expect(sanitize('<form>$child</form>'), equals(''));
}
});
});
test('SHOULD persist value src attribute of IMG tag WHEN it is CID string', () {
const inputHtml = '<img src="cid:email123">';
final result = transformer.process(inputHtml, htmlEscape);
group('FORM combined vectors fully sanitized', () {
test('SHOULD remove dangerous FORM attributes AND dangerous children', () {
const html =
'<form action="https://evil.com" method="POST"><input type="text"></form>';
expect(sanitize(html), equals(''));
});
expect(result, equals('<img src="cid:email123">'));
test('SHOULD remove multiple dangerous items inside complex FORM', () {
const html = '''
<form action="javascript:1" method="POST">
<input type="text">
<input type="password">
<button>Submit</button>
</form>
''';
final out = sanitize(html);
expect(out.contains('input'), isFalse);
expect(out.contains('button'), isFalse);
expect(out.contains('action'), isFalse);
expect(out.contains('method'), isFalse);
});
test('SHOULD remove nested FORM elements', () {
const html = '<form action="outer"><form action="inner"></form></form>';
final out = sanitize(html);
expect(out.contains('action'), isFalse);
});
test('SHOULD keep safe nested elements even when FORM wrapper is removed', () {
const html =
'<form action="test"><div><div><p>Content</p></div></div></form>';
final out = sanitize(html);
expect(out.contains('action'), isFalse);
expect(out.contains('<p>'), isTrue);
});
});
test(
'SHOULD persist nav tag and remove href attribute of A tag '
'WHEN href is invalid',
() {
const inputHtml = '<nav href="javascript:alert(1)"></nav>';
final result = transformer.process(inputHtml, htmlEscape);
group('FORM safe attributes safely unwrapped', () {
test('SHOULD unwrap FORM with safe style', () {
expect(sanitize('<form style="color: red;"></form>'), contains(''));
});
expect(result, equals('<nav></nav>'));
test('SHOULD unwrap FORM with safe class', () {
expect(sanitize('<form class="email-form"></form>'), contains(''));
});
test('SHOULD unwrap FORM with safe id', () {
expect(sanitize('<form id="my-form"></form>'), contains(''));
});
test('SHOULD unwrap FORM with safe bgcolor', () {
expect(sanitize('<form bgcolor="#fff"></form>'), contains(''));
});
test('SHOULD remove dangerous attributes even when safe attributes exist', () {
const html =
'<form style="color:red;" action="https://evil" class="a" method="POST"></form>';
final out = sanitize(html);
expect(out.contains('style'), isFalse);
expect(out.contains('class'), isFalse);
expect(out.contains('action'), isFalse);
expect(out.contains('method'), isFalse);
});
test('SHOULD preserve nested safe elements when FORM wrapper is removed', () {
const html =
'<form><div><p>Safe</p><table><tr><td>X</td></tr></table></div></form>';
final out = sanitize(html);
expect(out.contains('<div>'), isTrue);
expect(out.contains('Safe'), isTrue);
expect(out.contains('<table>'), isTrue);
});
});
test(
'SHOULD persist main tag and remove href attribute of A tag '
'WHEN href is invalid',
() {
const inputHtml = '<main href="javascript:alert(1)"></main>';
final result = transformer.process(inputHtml, htmlEscape);
group('Real-world phishing/XSS form attacks fully sanitized', () {
test('SHOULD remove credential phishing forms', () {
const html = '''
<form action="https://attacker.com" method="POST">
<input type="text">
<input type="password">
<button>Login</button>
</form>
''';
expect(result, equals('<main></main>'));
});
final out = sanitize(html);
expect(out.contains('action'), isFalse);
expect(out.contains('input'), isFalse);
expect(out.contains('button'), isFalse);
});
test(
'SHOULD persist footer tag and remove href attribute of A tag '
'WHEN href is invalid',
() {
const inputHtml = '<footer href="javascript:alert(1)"></footer>';
final result = transformer.process(inputHtml, htmlEscape);
test('SHOULD prevent CSRF token stealing by removing hidden inputs', () {
const html =
'<form action="https://bank.com"><input type="hidden" name="csrf"></form>';
final out = sanitize(html);
expect(out.contains('input'), isFalse);
expect(out.contains('csrf'), isFalse);
});
expect(result, equals('<footer></footer>'));
});
test('SHOULD remove javascript-based form actions', () {
expect(sanitize('<form action="javascript:alert(1)"></form>'), equals(''));
});
test(
'SHOULD persist supress_time_adjustment tag and remove href attribute of A tag '
'WHEN href is invalid',
() {
const inputHtml = '<supress_time_adjustment href="javascript:alert(1)"></supress_time_adjustment>';
final result = transformer.process(inputHtml, htmlEscape);
test('SHOULD remove data URI form actions', () {
expect(sanitize('<form action="data:text/html,<script>x</script>"></form>'),
equals(''));
});
expect(result, equals('<supress_time_adjustment></supress_time_adjustment>'));
test('SHOULD prevent file upload exploitation', () {
const html =
'<form action="/upload" enctype="multipart/form-data"><input type="file"></form>';
final out = sanitize(html);
expect(out.contains('input'), isFalse);
expect(out.contains('enctype'), isFalse);
});
test('SHOULD allow safe cosmetic FORM usage while removing wrapper', () {
const html = '''
<form style="border:1px solid #ccc">
<div><p>This is a survey notification</p></div>
</form>
''';
final out = sanitize(html);
expect(out.contains('<form'), isFalse);
expect(out.contains('style'), isFalse);
expect(out.contains('This is a survey notification'), isTrue);
expect(out.contains('<div'), isTrue);
expect(out.contains('<p'), isTrue);
});
test('SHOULD remove mixed XSS and phishing vectors inside FORM', () {
const html = '''
<form action="https://evil.com" onsubmit="alert(1)">
<script>steal()</script>
<input type="text" onclick="hack()">
<iframe src="x"></iframe>
</form>
''';
final out = sanitize(html);
expect(out.contains('action'), isFalse);
expect(out.contains('onsubmit'), isFalse);
expect(out.contains('script'), isFalse);
expect(out.contains('input'), isFalse);
expect(out.contains('iframe'), isFalse);
});
});
});
}