From f64cc96771cee467bf88d993d70eed846716ef39 Mon Sep 17 00:00:00 2001 From: dab246 Date: Wed, 23 Oct 2024 15:53:02 +0700 Subject: [PATCH 1/9] Standardize HTML sanitizing when preview email --- contact/pubspec.lock | 9 ++++ .../message_content_transformer.dart | 41 +++++++++++-------- .../utils/html_transformer/sanitize_html.dart | 19 +++++++++ ...ndardize_html_sanitizing_transformers.dart | 39 ++++++++++++++++++ .../transform_configuration.dart | 25 +++++++---- core/pubspec.lock | 9 ++++ core/pubspec.yaml | 6 +++ ...ize_html_sanitizing_transformers_test.dart | 40 ++++++++++++++++++ .../email/data/local/html_analyzer.dart | 5 ++- model/pubspec.lock | 9 ++++ pubspec.lock | 9 ++++ 11 files changed, 185 insertions(+), 26 deletions(-) create mode 100644 core/lib/presentation/utils/html_transformer/sanitize_html.dart create mode 100644 core/lib/presentation/utils/html_transformer/text/standardize_html_sanitizing_transformers.dart create mode 100644 core/test/utils/standardize_html_sanitizing_transformers_test.dart diff --git a/contact/pubspec.lock b/contact/pubspec.lock index ce6c986509..e92a2e3b7e 100644 --- a/contact/pubspec.lock +++ b/contact/pubspec.lock @@ -992,6 +992,15 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.1" + sanitize_html: + dependency: transitive + description: + path: sanitize_html + ref: twake-supported + resolved-ref: "5b9986c42cc86c7e565c941648f875f2e1d2f7bd" + url: "https://github.com/dab246/dart-neats.git" + source: git + version: "2.1.0" shelf: dependency: transitive description: diff --git a/core/lib/presentation/utils/html_transformer/message_content_transformer.dart b/core/lib/presentation/utils/html_transformer/message_content_transformer.dart index 034855b738..a42a5024f7 100644 --- a/core/lib/presentation/utils/html_transformer/message_content_transformer.dart +++ b/core/lib/presentation/utils/html_transformer/message_content_transformer.dart @@ -23,13 +23,12 @@ class MessageContentTransformer { Map? mapUrlDownloadCID }) async { await Future.wait([ - if (_configuration.domTransformers.isNotEmpty) - ..._configuration.domTransformers.map((domTransformer) async => - domTransformer.process( - document: document, - dioClient: _dioClient, - mapUrlDownloadCID: mapUrlDownloadCID, - ) + ..._configuration.domTransformers.map((domTransformer) async => + domTransformer.process( + document: document, + dioClient: _dioClient, + mapUrlDownloadCID: mapUrlDownloadCID, + ) ) ]); } @@ -38,24 +37,32 @@ class MessageContentTransformer { required String message, Map? mapUrlDownloadCID }) async { - final document = parse(message); - await _transformDocument( - document: document, - mapUrlDownloadCID: mapUrlDownloadCID, - ); + final newMessage = _configuration.textTransformers.isNotEmpty + ? _transformMessage(message) + : message; + + final document = parse(newMessage); + + if (_configuration.domTransformers.isNotEmpty) { + await _transformDocument( + document: document, + mapUrlDownloadCID: mapUrlDownloadCID, + ); + } + return document; } String _transformMessage(String message) { - if (_configuration.textTransformers.isNotEmpty) { - for (var transformer in _configuration.textTransformers) { - message = transformer.process(message, _htmlEscape); - } + for (var transformer in _configuration.textTransformers) { + message = transformer.process(message, _htmlEscape); } return message; } String toMessage(String message) { - return _transformMessage(message); + return _configuration.textTransformers.isNotEmpty + ? _transformMessage(message) + : message; } } \ No newline at end of file diff --git a/core/lib/presentation/utils/html_transformer/sanitize_html.dart b/core/lib/presentation/utils/html_transformer/sanitize_html.dart new file mode 100644 index 0000000000..b51e3ac6ad --- /dev/null +++ b/core/lib/presentation/utils/html_transformer/sanitize_html.dart @@ -0,0 +1,19 @@ +import 'package:sanitize_html/sanitize_html.dart'; + +class SanitizeHtml { + String process({ + required String inputHtml, + List? allowAttributes, + List? allowTags, + List? allowClassNames, + }) { + final outputHtml = sanitizeHtml( + inputHtml, + allowAttributes: allowAttributes, + allowTags: allowTags, + allowClassName: (className) => + allowClassNames?.contains(className.toLowerCase()) == true + ); + return outputHtml; + } +} \ No newline at end of file diff --git a/core/lib/presentation/utils/html_transformer/text/standardize_html_sanitizing_transformers.dart b/core/lib/presentation/utils/html_transformer/text/standardize_html_sanitizing_transformers.dart new file mode 100644 index 0000000000..588ed66c15 --- /dev/null +++ b/core/lib/presentation/utils/html_transformer/text/standardize_html_sanitizing_transformers.dart @@ -0,0 +1,39 @@ +import 'dart:convert'; +import 'package:core/presentation/utils/html_transformer/base/text_transformer.dart'; +import 'package:core/presentation/utils/html_transformer/sanitize_html.dart'; + +class StandardizeHtmlSanitizingTransformers extends TextTransformer { + + static const List mailAllowedHtmlAttributes = [ + 'style', + 'public-asset-id', + 'data-filename', + 'bgcolor', + ]; + + static const List mailAllowedHtmlTags = [ + 'font', + 'u', + 'mcourbo@linagora.com', + 'center', + ]; + + static const List mailAllowedHtmlClassNames = [ + 'tmail-signature', + 'tmail-signature-blocked', + 'tmail-signature-button', + 'tmail-signature-content', + 'tmail_signature_prefix', + ]; + + const StandardizeHtmlSanitizingTransformers(); + + @override + String process(String text, HtmlEscape htmlEscape) => + SanitizeHtml().process( + inputHtml: text, + allowAttributes: mailAllowedHtmlAttributes, + allowTags: mailAllowedHtmlTags, + allowClassNames: mailAllowedHtmlClassNames, + ); +} diff --git a/core/lib/presentation/utils/html_transformer/transform_configuration.dart b/core/lib/presentation/utils/html_transformer/transform_configuration.dart index 4b277d40c1..36cf86077a 100644 --- a/core/lib/presentation/utils/html_transformer/transform_configuration.dart +++ b/core/lib/presentation/utils/html_transformer/transform_configuration.dart @@ -15,7 +15,7 @@ import 'package:core/presentation/utils/html_transformer/dom/remove_tooltip_link import 'package:core/presentation/utils/html_transformer/dom/sanitize_hyper_link_tag_in_html_transformers.dart'; import 'package:core/presentation/utils/html_transformer/dom/script_transformers.dart'; import 'package:core/presentation/utils/html_transformer/dom/signature_transformers.dart'; -import 'package:core/presentation/utils/html_transformer/text/sanitize_autolink_html_transformers.dart'; +import 'package:core/presentation/utils/html_transformer/text/standardize_html_sanitizing_transformers.dart'; import 'package:core/utils/platform_info.dart'; /// Contains the configuration for all transformations. @@ -37,7 +37,9 @@ class TransformConfiguration { factory TransformConfiguration.fromDomTransformers(List domTransformers) => TransformConfiguration(domTransformers, []); - factory TransformConfiguration.empty() => const TransformConfiguration([], []); + factory TransformConfiguration.fromTextTransformers( + List textTransformers + ) => TransformConfiguration([], textTransformers); factory TransformConfiguration.forReplyForwardEmail() => TransformConfiguration.fromDomTransformers([ if (PlatformInfo.isWeb) @@ -46,10 +48,15 @@ class TransformConfiguration { const RemoveCollapsedSignatureButtonTransformer(), ]); - factory TransformConfiguration.forDraftsEmail() => TransformConfiguration.fromDomTransformers([const ImageTransformer()]); - factory TransformConfiguration.forEditDraftsEmail() => TransformConfiguration.fromDomTransformers([ - ...TransformConfiguration.forDraftsEmail().domTransformers, - const HideDraftSignatureTransformer()]); + factory TransformConfiguration.forDraftsEmail() => TransformConfiguration.create( + customDomTransformers: [const ImageTransformer()] + ); + factory TransformConfiguration.forEditDraftsEmail() => TransformConfiguration.create( + customDomTransformers: [ + ...TransformConfiguration.forDraftsEmail().domTransformers, + const HideDraftSignatureTransformer() + ] + ); factory TransformConfiguration.forPreviewEmailOnWeb() => TransformConfiguration.create( customDomTransformers: [ @@ -65,7 +72,9 @@ class TransformConfiguration { factory TransformConfiguration.forPreviewEmail() => TransformConfiguration.standardConfiguration; - factory TransformConfiguration.forRestoreEmail() => TransformConfiguration.fromDomTransformers([const ImageTransformer()]); + factory TransformConfiguration.forRestoreEmail() => TransformConfiguration.create( + customDomTransformers: [const ImageTransformer()] + ); factory TransformConfiguration.forPrintEmail() => TransformConfiguration.fromDomTransformers([ if (PlatformInfo.isWeb) @@ -115,6 +124,6 @@ class TransformConfiguration { ]; static const List standardTextTransformers = [ - SanitizeAutolinkHtmlTransformers() + StandardizeHtmlSanitizingTransformers(), ]; } \ No newline at end of file diff --git a/core/pubspec.lock b/core/pubspec.lock index b520f72981..287165a1e3 100644 --- a/core/pubspec.lock +++ b/core/pubspec.lock @@ -945,6 +945,15 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + sanitize_html: + dependency: "direct main" + description: + path: sanitize_html + ref: twake-supported + resolved-ref: "5b9986c42cc86c7e565c941648f875f2e1d2f7bd" + url: "https://github.com/dab246/dart-neats.git" + source: git + version: "2.1.0" shelf: dependency: transitive description: diff --git a/core/pubspec.yaml b/core/pubspec.yaml index b376cd7b9f..96529670d2 100644 --- a/core/pubspec.yaml +++ b/core/pubspec.yaml @@ -34,6 +34,12 @@ dependencies: url: https://github.com/dab246/languagetool_textfield.git ref: twake-supported + sanitize_html: + git: + url: https://github.com/dab246/dart-neats.git + ref: twake-supported + path: sanitize_html + ### Dependencies from pub.dev ### cupertino_icons: 1.0.6 diff --git a/core/test/utils/standardize_html_sanitizing_transformers_test.dart b/core/test/utils/standardize_html_sanitizing_transformers_test.dart new file mode 100644 index 0000000000..0f44b48025 --- /dev/null +++ b/core/test/utils/standardize_html_sanitizing_transformers_test.dart @@ -0,0 +1,40 @@ +import 'package:core/presentation/utils/html_transformer/text/standardize_html_sanitizing_transformers.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'dart:convert'; + +void main() { + group('StandardizeHtmlSanitizingTransformers::test', () { + const transformer = StandardizeHtmlSanitizingTransformers(); + const htmlEscape = HtmlEscape(); + + test('SHOULD remove attributes of IMG tag WHEN they are invalid', () { + const inputHtml = ''; + final result = transformer.process(inputHtml, htmlEscape); + + expect(result, equals('')); + }); + + test('SHOULD remove all SCRIPTS tags', () { + const inputHtml = ''' + + '''; + final result = transformer.process(inputHtml, htmlEscape).trim(); + + expect(result, equals('')); + }); + + test('SHOULD remove all IFRAME tags', () { + const inputHtml = '