[BUG] Remove HtmlMessagePurifier when not used
This commit is contained in:
@@ -62,9 +62,6 @@ export 'presentation/state/success.dart';
|
||||
export 'presentation/state/failure.dart';
|
||||
export 'presentation/state/app_state.dart';
|
||||
|
||||
// Validator
|
||||
export 'presentation/validator/html_message_purifier.dart';
|
||||
|
||||
// Local
|
||||
export 'data/local/config/database_config.dart';
|
||||
export 'data/local/config/email_address_table.dart';
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import 'sane_html_validator.dart' show SaneHtmlValidator;
|
||||
import 'package:core/core.dart';
|
||||
|
||||
class HtmlMessagePurifier {
|
||||
String purifyHtmlMessage(
|
||||
String html,
|
||||
{
|
||||
Set<String>? allowElementIds,
|
||||
Set<String>? allowClassNames,
|
||||
Set<String>? allowAttributes
|
||||
}
|
||||
) {
|
||||
return sanitizeHtml(
|
||||
html,
|
||||
allowElementId: (elementId) => allowElementIds != null ? allowElementIds.contains(elementId) : false,
|
||||
allowClassName: (className) => allowClassNames != null ? allowClassNames.contains(className) : false,
|
||||
allowAttributes: (attribute) => allowAttributes != null ? allowAttributes.contains(attribute) : false)
|
||||
.changeStyleBackground()
|
||||
.removeFontSizeZeroPixel()
|
||||
.removeMaxHeightZeroPixel()
|
||||
.removeMaxWidthZeroPixel()
|
||||
.removeHeightZeroPixel()
|
||||
.removeWidthZeroPixel()
|
||||
.addBorderLefForBlockQuote();
|
||||
}
|
||||
|
||||
String sanitizeHtml(
|
||||
String htmlString,
|
||||
{
|
||||
bool Function(String)? allowElementId,
|
||||
bool Function(String)? allowClassName,
|
||||
bool Function(String)? allowAttributes,
|
||||
Iterable<String>? Function(String)? addLinkRel
|
||||
}
|
||||
) {
|
||||
return SaneHtmlValidator(
|
||||
allowElementId: allowElementId,
|
||||
allowClassName: allowClassName,
|
||||
allowAttributes: allowAttributes,
|
||||
addLinkRel: addLinkRel,
|
||||
).sanitize(htmlString);
|
||||
}
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
|
||||
import 'package:html/dom.dart';
|
||||
import 'package:html/parser.dart' as html_parser;
|
||||
|
||||
final _allowedElements = <String>{
|
||||
'H1',
|
||||
'H2',
|
||||
'H3',
|
||||
'H4',
|
||||
'H5',
|
||||
'H6',
|
||||
'H7',
|
||||
'H8',
|
||||
'BR',
|
||||
'B',
|
||||
'I',
|
||||
'STRONG',
|
||||
'EM',
|
||||
'A',
|
||||
'PRE',
|
||||
'CODE',
|
||||
'IMG',
|
||||
'TT',
|
||||
'DIV',
|
||||
'INS',
|
||||
'DEL',
|
||||
'SUP',
|
||||
'SUB',
|
||||
'P',
|
||||
'OL',
|
||||
'UL',
|
||||
'TABLE',
|
||||
'THEAD',
|
||||
'TBODY',
|
||||
'TFOOT',
|
||||
'BLOCKQUOTE',
|
||||
'DL',
|
||||
'DT',
|
||||
'DD',
|
||||
'KBD',
|
||||
'Q',
|
||||
'SAMP',
|
||||
'VAR',
|
||||
'HR',
|
||||
'RUBY',
|
||||
'RT',
|
||||
'RP',
|
||||
'LI',
|
||||
'TR',
|
||||
'TD',
|
||||
'TH',
|
||||
'S',
|
||||
'STRIKE',
|
||||
'SUMMARY',
|
||||
'DETAILS',
|
||||
'CAPTION',
|
||||
'FIGURE',
|
||||
'FIGCAPTION',
|
||||
'ABBR',
|
||||
'BDO',
|
||||
'CITE',
|
||||
'DFN',
|
||||
'MARK',
|
||||
'SMALL',
|
||||
'SPAN',
|
||||
'TIME',
|
||||
'WBR',
|
||||
};
|
||||
|
||||
final _alwaysAllowedAttributes = <String>{
|
||||
'abbr',
|
||||
'accept',
|
||||
'accept-charset',
|
||||
'accesskey',
|
||||
'action',
|
||||
'align',
|
||||
'alt',
|
||||
'aria-describedby',
|
||||
'aria-hidden',
|
||||
'aria-label',
|
||||
'aria-labelledby',
|
||||
'axis',
|
||||
'border',
|
||||
'cellpadding',
|
||||
'cellspacing',
|
||||
'char',
|
||||
'charoff',
|
||||
'charset',
|
||||
'checked',
|
||||
'clear',
|
||||
'cols',
|
||||
'colspan',
|
||||
'color',
|
||||
'compact',
|
||||
'coords',
|
||||
'datetime',
|
||||
'dir',
|
||||
'disabled',
|
||||
'enctype',
|
||||
'for',
|
||||
'frame',
|
||||
'headers',
|
||||
'height',
|
||||
'hreflang',
|
||||
'hspace',
|
||||
'ismap',
|
||||
'label',
|
||||
'lang',
|
||||
'maxlength',
|
||||
'media',
|
||||
'method',
|
||||
'multiple',
|
||||
'name',
|
||||
'nohref',
|
||||
'noshade',
|
||||
'nowrap',
|
||||
'open',
|
||||
'prompt',
|
||||
'readonly',
|
||||
'rel',
|
||||
'rev',
|
||||
'rows',
|
||||
'rowspan',
|
||||
'rules',
|
||||
'scope',
|
||||
'selected',
|
||||
'shape',
|
||||
'size',
|
||||
'span',
|
||||
'start',
|
||||
'summary',
|
||||
'tabindex',
|
||||
'target',
|
||||
'title',
|
||||
'type',
|
||||
'usemap',
|
||||
'valign',
|
||||
'value',
|
||||
'vspace',
|
||||
'width',
|
||||
'itemprop',
|
||||
};
|
||||
|
||||
bool _alwaysAllowed(String _) => true;
|
||||
|
||||
bool _validLink(String url) {
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
return uri.isScheme('https') ||
|
||||
uri.isScheme('http') ||
|
||||
uri.isScheme('mailto') ||
|
||||
!uri.hasScheme;
|
||||
} on FormatException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool _validUrl(String url) {
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
return uri.isScheme('https') || uri.isScheme('http') || !uri.hasScheme;
|
||||
} on FormatException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final _citeAttributeValidator = <String, bool Function(String)>{
|
||||
'cite': _validUrl,
|
||||
};
|
||||
|
||||
final _elementAttributeValidators =
|
||||
<String, Map<String, bool Function(String)>>{
|
||||
'A': {
|
||||
'href': _validLink,
|
||||
},
|
||||
'IMG': {
|
||||
'src': _alwaysAllowed,
|
||||
'longdesc': _validUrl,
|
||||
},
|
||||
'DIV': {
|
||||
'itemscope': _alwaysAllowed,
|
||||
'itemtype': _alwaysAllowed,
|
||||
},
|
||||
'BLOCKQUOTE': _citeAttributeValidator,
|
||||
'DEL': _citeAttributeValidator,
|
||||
'INS': _citeAttributeValidator,
|
||||
'Q': _citeAttributeValidator,
|
||||
};
|
||||
|
||||
/// An implementation of [html.NodeValidator] that only allows sane HTML tags
|
||||
/// and attributes protecting against XSS.
|
||||
///
|
||||
/// Modeled after the [rules employed by Github][1] when sanitizing GFM (Github
|
||||
/// Flavored Markdown). Notably this excludes CSS styles and other tags that
|
||||
/// easily interferes with the rest of the page.
|
||||
///
|
||||
/// [1]: https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/sanitization_filter.rb
|
||||
class SaneHtmlValidator {
|
||||
final bool Function(String)? allowElementId;
|
||||
final bool Function(String)? allowClassName;
|
||||
final bool Function(String)? allowAttributes;
|
||||
final Iterable<String>? Function(String)? addLinkRel;
|
||||
|
||||
SaneHtmlValidator({
|
||||
required this.allowElementId,
|
||||
required this.allowClassName,
|
||||
required this.allowAttributes,
|
||||
required this.addLinkRel,
|
||||
});
|
||||
|
||||
String sanitize(String htmlString) {
|
||||
final root = html_parser.parseFragment(htmlString);
|
||||
_sanitize(root);
|
||||
return root.outerHtml;
|
||||
}
|
||||
|
||||
void _sanitize(Node node) {
|
||||
if (node is Element) {
|
||||
final tagName = node.localName!.toUpperCase();
|
||||
if (!_allowedElements.contains(tagName)) {
|
||||
node.remove();
|
||||
return;
|
||||
}
|
||||
node.attributes.removeWhere((k, v) {
|
||||
final attrName = k.toString();
|
||||
if (attrName == 'id') {
|
||||
return allowElementId == null || !allowElementId!(v);
|
||||
}
|
||||
if (attrName == 'class') {
|
||||
if (allowClassName == null) return true;
|
||||
node.classes.removeWhere((cn) => !allowClassName!(cn));
|
||||
return node.classes.isEmpty;
|
||||
}
|
||||
|
||||
return !_isAttributeAllowed(tagName, attrName, v);
|
||||
});
|
||||
if (tagName == 'A') {
|
||||
final href = node.attributes['href'];
|
||||
if (href != null && addLinkRel != null) {
|
||||
final rels = addLinkRel!(href);
|
||||
if (rels != null && rels.isNotEmpty) {
|
||||
node.attributes['rel'] = rels.join(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.hasChildNodes()) {
|
||||
// doing it in reverse order, because we could otherwise skip one, when a
|
||||
// node is removed...
|
||||
for (var i = node.nodes.length - 1; i >= 0; i--) {
|
||||
_sanitize(node.nodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _isAttributeAllowed(String tagName, String attrName, String value) {
|
||||
if (_alwaysAllowedAttributes.contains(attrName)) return true;
|
||||
|
||||
if (allowAttributes != null && allowAttributes!(attrName)) return true;
|
||||
|
||||
// Special validators for special attributes on special tags (href/src/cite)
|
||||
final attributeValidators = _elementAttributeValidators[tagName];
|
||||
if (attributeValidators == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final validator = attributeValidators[attrName];
|
||||
if (validator == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return validator(value);
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import 'package:core/presentation/validator/sane_html_validator.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('sane_html_validator test', () {
|
||||
|
||||
test('sanitize should return clean html not contain style attribute', () async {
|
||||
|
||||
final saneHtmlValidator = SaneHtmlValidator(
|
||||
allowAttributes: null,
|
||||
allowClassName: null,
|
||||
allowElementId: null,
|
||||
addLinkRel: null
|
||||
);
|
||||
|
||||
final cleanHtml = saneHtmlValidator.sanitize('<a style="background-color: #FF0000" class="className" id="idElement" href="javascript:alert()">Hello</a>');
|
||||
|
||||
expect(cleanHtml, isNot(contains('style')));
|
||||
expect(cleanHtml, isNot(contains('className')));
|
||||
expect(cleanHtml, isNot(contains('idElement')));
|
||||
expect(cleanHtml, isNot(contains('href')));
|
||||
});
|
||||
|
||||
test('sanitize should return clean html contain style attribute', () async {
|
||||
|
||||
final saneHtmlValidator = SaneHtmlValidator(
|
||||
allowAttributes: (attribute) => attribute == 'style',
|
||||
allowClassName: null,
|
||||
allowElementId: null,
|
||||
addLinkRel: null
|
||||
);
|
||||
|
||||
final cleanHtml = saneHtmlValidator.sanitize('<a style="background-color: #FF0000">Hello</a>');
|
||||
|
||||
expect(cleanHtml, contains('style'));
|
||||
});
|
||||
|
||||
test('sanitize should return clean html contain test class name', () async {
|
||||
|
||||
final saneHtmlValidator = SaneHtmlValidator(
|
||||
allowAttributes: null,
|
||||
allowClassName: (className) => className == 'test',
|
||||
allowElementId: null,
|
||||
addLinkRel: null
|
||||
);
|
||||
|
||||
final cleanHtml = saneHtmlValidator.sanitize('<span class="test">Hello</span');
|
||||
|
||||
expect(cleanHtml, contains('test'));
|
||||
});
|
||||
|
||||
test('sanitize should return clean html contain test element id', () async {
|
||||
|
||||
final saneHtmlValidator = SaneHtmlValidator(
|
||||
allowAttributes: null,
|
||||
allowClassName: null,
|
||||
allowElementId: (elementId) => elementId == 'test',
|
||||
addLinkRel: null
|
||||
);
|
||||
|
||||
final cleanHtml = saneHtmlValidator.sanitize('<span id="test">Hello</span');
|
||||
|
||||
expect(cleanHtml, contains('test'));
|
||||
});
|
||||
|
||||
test('sanitize should return clean html contain rel="nofollow" in tag', () async {
|
||||
|
||||
final saneHtmlValidator = SaneHtmlValidator(
|
||||
allowAttributes: null,
|
||||
allowClassName: null,
|
||||
allowElementId: null,
|
||||
addLinkRel: (href) => href == 'http://example.com/test.jpg' ? ['nofollow'] : null,
|
||||
);
|
||||
|
||||
final cleanHtml = saneHtmlValidator.sanitize('<a href="http://example.com/test.jpg">Hello</a>');
|
||||
|
||||
expect(cleanHtml, contains('rel="nofollow"'));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -73,7 +73,6 @@ class ComposerBindings extends Bindings {
|
||||
Get.find<AppToast>(),
|
||||
Get.find<Uuid>(),
|
||||
Get.find<TextEditingController>(),
|
||||
Get.find<HtmlMessagePurifier>(),
|
||||
Get.find<LocalFilePickerInteractor>(),
|
||||
Get.find<UploadMultipleAttachmentInteractor>()));
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ class ComposerController extends BaseController {
|
||||
final AppToast _appToast;
|
||||
final Uuid _uuid;
|
||||
final TextEditingController subjectEmailInputController;
|
||||
final HtmlMessagePurifier _htmlMessagePurifier;
|
||||
final LocalFilePickerInteractor _localFilePickerInteractor;
|
||||
final UploadMultipleAttachmentInteractor _uploadMultipleAttachmentInteractor;
|
||||
|
||||
@@ -73,7 +72,6 @@ class ComposerController extends BaseController {
|
||||
this._appToast,
|
||||
this._uuid,
|
||||
this.subjectEmailInputController,
|
||||
this._htmlMessagePurifier,
|
||||
this._localFilePickerInteractor,
|
||||
this._uploadMultipleAttachmentInteractor,
|
||||
);
|
||||
@@ -138,7 +136,7 @@ class ComposerController extends BaseController {
|
||||
if (composerArguments.value != null
|
||||
&& composerArguments.value!.emailActionType != EmailActionType.compose
|
||||
&& Get.context != null) {
|
||||
final contentEmailQuoted = _getBodyEmailQuotedAsHtml(Get.context!, _htmlMessagePurifier);
|
||||
final contentEmailQuoted = _getBodyEmailQuotedAsHtml(Get.context!);
|
||||
return contentEmailQuoted;
|
||||
}
|
||||
return '';
|
||||
@@ -210,7 +208,7 @@ class ComposerController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
String _getBodyEmailQuotedAsHtml(BuildContext context, HtmlMessagePurifier htmlMessagePurifier) {
|
||||
String _getBodyEmailQuotedAsHtml(BuildContext context) {
|
||||
final headerEmailQuoted = getHeaderEmailQuoted(Localizations.localeOf(context).toLanguageTag());
|
||||
|
||||
final headerEmailQuotedAsHtml = headerEmailQuoted != null
|
||||
|
||||
@@ -23,7 +23,6 @@ class EmailView extends GetView {
|
||||
final emailController = Get.find<EmailController>();
|
||||
final responsiveUtils = Get.find<ResponsiveUtils>();
|
||||
final imagePaths = Get.find<ImagePaths>();
|
||||
final htmlMessagePurifier = Get.find<HtmlMessagePurifier>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -279,6 +278,7 @@ class EmailView extends GetView {
|
||||
switch(emailController.emailContent.value!.type) {
|
||||
case EmailContentType.textHtml:
|
||||
return HtmlContentViewer(
|
||||
minHeight: 400,
|
||||
contentHtml: emailController.emailContent.value!.content,
|
||||
onLoadStart: (webController, uri) => emailController.dispatchState(Right(WebViewLoadingState())),
|
||||
onLoadStop: (webController, uri) => emailController.dispatchState(Right(WebViewLoadCompletedState())),
|
||||
|
||||
@@ -13,7 +13,7 @@ class CoreBindings extends Bindings {
|
||||
_bindingAppImagePaths();
|
||||
_bindingResponsiveManager();
|
||||
_bindingKeyboardManager();
|
||||
_bindingValidator();
|
||||
_bindingTransformer();
|
||||
_bindingToast();
|
||||
_bindingDeviceManager();
|
||||
}
|
||||
@@ -34,8 +34,7 @@ class CoreBindings extends Bindings {
|
||||
Get.put(KeyboardUtils());
|
||||
}
|
||||
|
||||
void _bindingValidator() {
|
||||
Get.put(HtmlMessagePurifier());
|
||||
void _bindingTransformer() {
|
||||
Get.put(HtmlAnalyzer());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user