Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standardize HTML sanitizing when preview email #3223

Merged
merged 9 commits into from
Oct 24, 2024
9 changes: 9 additions & 0 deletions contact/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,15 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.2.1"
sanitize_html:
dab246 marked this conversation as resolved.
Show resolved Hide resolved
dependency: transitive
description:
path: sanitize_html
ref: support_mail
resolved-ref: c663ad93a659fce3c0d209a048fa93c6465ebedc
url: "https://github.com/linagora/dart-neats.git"
source: git
version: "2.1.0"
shelf:
dependency: transitive
description:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,12 @@ class MessageContentTransformer {
Map<String, String>? 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,
)
)
]);
}
Expand All @@ -38,24 +37,32 @@ class MessageContentTransformer {
required String message,
Map<String, String>? 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;
}
}
19 changes: 19 additions & 0 deletions core/lib/presentation/utils/html_transformer/sanitize_html.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:sanitize_html/sanitize_html.dart';

class SanitizeHtml {
String process({
required String inputHtml,
List<String>? allowAttributes,
List<String>? allowTags,
List<String>? allowClassNames,
}) {
final outputHtml = sanitizeHtml(
inputHtml,
allowAttributes: allowAttributes,
allowTags: allowTags,
allowClassName: (className) =>
allowClassNames?.contains(className.toLowerCase()) == true
);
return outputHtml;
}
}
Original file line number Diff line number Diff line change
@@ -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<String> mailAllowedHtmlAttributes = [
'style',
'public-asset-id',
'data-filename',
'bgcolor',
];

static const List<String> mailAllowedHtmlTags = [
'font',
'u',
'[email protected]',
dab246 marked this conversation as resolved.
Show resolved Hide resolved
'center',
];

static const List<String> 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(
hoangdat marked this conversation as resolved.
Show resolved Hide resolved
inputHtml: text,
allowAttributes: mailAllowedHtmlAttributes,
allowTags: mailAllowedHtmlTags,
allowClassNames: mailAllowedHtmlClassNames,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -37,7 +37,9 @@ class TransformConfiguration {

factory TransformConfiguration.fromDomTransformers(List<DomTransformer> domTransformers) => TransformConfiguration(domTransformers, []);

factory TransformConfiguration.empty() => const TransformConfiguration([], []);
factory TransformConfiguration.fromTextTransformers(
List<TextTransformer> textTransformers
) => TransformConfiguration([], textTransformers);

factory TransformConfiguration.forReplyForwardEmail() => TransformConfiguration.fromDomTransformers([
if (PlatformInfo.isWeb)
Expand All @@ -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: [
Expand All @@ -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)
Expand Down Expand Up @@ -115,6 +124,6 @@ class TransformConfiguration {
];

static const List<TextTransformer> standardTextTransformers = [
SanitizeAutolinkHtmlTransformers()
StandardizeHtmlSanitizingTransformers(),
];
}
9 changes: 9 additions & 0 deletions core/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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: support_mail
resolved-ref: c663ad93a659fce3c0d209a048fa93c6465ebedc
url: "https://github.com/linagora/dart-neats.git"
source: git
version: "2.1.0"
shelf:
dependency: transitive
description:
Expand Down
9 changes: 9 additions & 0 deletions core/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ dependencies:
url: https://github.com/dab246/languagetool_textfield.git
ref: twake-supported

# Sanitize_html is restricting Tags and Attributes. So some of our own tags and attributes (signature, public asset,...) will be lost when sanitizing html.
# TODO: We will change it when the PR in upstream repository will be merged
# https://github.com/google/dart-neats/pull/259
dab246 marked this conversation as resolved.
Show resolved Hide resolved
sanitize_html:
git:
url: https://github.com/linagora/dart-neats.git
ref: support_mail
path: sanitize_html

### Dependencies from pub.dev ###
cupertino_icons: 1.0.6

Expand Down
40 changes: 40 additions & 0 deletions core/test/utils/standardize_html_sanitizing_transformers_test.dart
Original file line number Diff line number Diff line change
@@ -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 = '<img src="1" href="1" onerror="javascript:alert(1)">';
hoangdat marked this conversation as resolved.
Show resolved Hide resolved
final result = transformer.process(inputHtml, htmlEscape);

expect(result, equals('<img src="1">'));
});

test('SHOULD remove all SCRIPTS tags', () {
const inputHtml = '''
</script><img/*%00/src="worksinchrome&colon;prompt&#x28;1&#x29;"/%00*/onerror='eval(src)'>
''';
final result = transformer.process(inputHtml, htmlEscape).trim();

expect(result, equals('<img>'));
});

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);

expect(result, equals(''));
});

test('SHOULD remove href attribute of A tag WHEN it is invalid', () {
const inputHtml = '<a href="javas\x06cript:javascript:alert(1)" id="fuzzelement1">test</a>';
final result = transformer.process(inputHtml, htmlEscape);

expect(result, equals('<a>test</a>'));
});
});
}
5 changes: 4 additions & 1 deletion lib/features/email/data/local/html_analyzer.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:collection/collection.dart';
import 'package:core/data/constants/constant.dart';
import 'package:core/presentation/utils/html_transformer/html_transform.dart';
import 'package:core/presentation/utils/html_transformer/text/sanitize_autolink_html_transformers.dart';
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
import 'package:core/utils/app_logger.dart';
import 'package:dartz/dartz.dart';
Expand Down Expand Up @@ -35,7 +36,9 @@ class HtmlAnalyzer {
case EmailContentType.textPlain:
final message = _htmlTransform.transformToTextPlain(
content: emailContent.content,
transformConfiguration: transformConfiguration
transformConfiguration: TransformConfiguration.fromTextTransformers([
const SanitizeAutolinkHtmlTransformers()
]),
);
return EmailContent(emailContent.type, message);
default:
Expand Down
9 changes: 9 additions & 0 deletions model/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,15 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.2.1"
sanitize_html:
dependency: transitive
description:
path: sanitize_html
ref: support_mail
resolved-ref: c663ad93a659fce3c0d209a048fa93c6465ebedc
url: "https://github.com/linagora/dart-neats.git"
source: git
version: "2.1.0"
shelf:
dependency: transitive
description:
Expand Down
9 changes: 9 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1714,6 +1714,15 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.27.7"
sanitize_html:
dependency: transitive
description:
path: sanitize_html
ref: support_mail
resolved-ref: c663ad93a659fce3c0d209a048fa93c6465ebedc
url: "https://github.com/linagora/dart-neats.git"
source: git
version: "2.1.0"
server_settings:
dependency: "direct main"
description:
Expand Down
Loading