diff --git a/core/lib/core.dart b/core/lib/core.dart index 7945aed8e..de9c91d64 100644 --- a/core/lib/core.dart +++ b/core/lib/core.dart @@ -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'; diff --git a/core/lib/presentation/validator/html_message_purifier.dart b/core/lib/presentation/validator/html_message_purifier.dart deleted file mode 100644 index e06bd9a6a..000000000 --- a/core/lib/presentation/validator/html_message_purifier.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'sane_html_validator.dart' show SaneHtmlValidator; -import 'package:core/core.dart'; - -class HtmlMessagePurifier { - String purifyHtmlMessage( - String html, - { - Set? allowElementIds, - Set? allowClassNames, - Set? 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? Function(String)? addLinkRel - } - ) { - return SaneHtmlValidator( - allowElementId: allowElementId, - allowClassName: allowClassName, - allowAttributes: allowAttributes, - addLinkRel: addLinkRel, - ).sanitize(htmlString); - } -} \ No newline at end of file diff --git a/core/lib/presentation/validator/sane_html_validator.dart b/core/lib/presentation/validator/sane_html_validator.dart deleted file mode 100644 index 2c4669d3a..000000000 --- a/core/lib/presentation/validator/sane_html_validator.dart +++ /dev/null @@ -1,274 +0,0 @@ - -import 'package:html/dom.dart'; -import 'package:html/parser.dart' as html_parser; - -final _allowedElements = { - '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 = { - '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 = { - 'cite': _validUrl, -}; - -final _elementAttributeValidators = - >{ - '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? 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); - } -} \ No newline at end of file diff --git a/core/test/sane_html_validator_test.dart b/core/test/sane_html_validator_test.dart deleted file mode 100644 index 6359094c4..000000000 --- a/core/test/sane_html_validator_test.dart +++ /dev/null @@ -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('Hello'); - - 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('Hello'); - - 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('Hello elementId == 'test', - addLinkRel: null - ); - - final cleanHtml = saneHtmlValidator.sanitize('Hello href == 'http://example.com/test.jpg' ? ['nofollow'] : null, - ); - - final cleanHtml = saneHtmlValidator.sanitize('Hello'); - - expect(cleanHtml, contains('rel="nofollow"')); - }); - }); -} \ No newline at end of file diff --git a/lib/features/composer/presentation/composer_bindings.dart b/lib/features/composer/presentation/composer_bindings.dart index df13c3714..cb7d15b22 100644 --- a/lib/features/composer/presentation/composer_bindings.dart +++ b/lib/features/composer/presentation/composer_bindings.dart @@ -73,7 +73,6 @@ class ComposerBindings extends Bindings { Get.find(), Get.find(), Get.find(), - Get.find(), Get.find(), Get.find())); } diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index ab2306d6c..b158ad1e7 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -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 diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index 2555c0527..e2dc8a97e 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -23,7 +23,6 @@ class EmailView extends GetView { final emailController = Get.find(); final responsiveUtils = Get.find(); final imagePaths = Get.find(); - final htmlMessagePurifier = Get.find(); @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())), diff --git a/lib/main/bindings/core/core_bindings.dart b/lib/main/bindings/core/core_bindings.dart index 02fcea767..e297d8fad 100644 --- a/lib/main/bindings/core/core_bindings.dart +++ b/lib/main/bindings/core/core_bindings.dart @@ -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()); }