From e7ffc01a919ff2583ce9f3c35f4e2c2753f738a5 Mon Sep 17 00:00:00 2001 From: Istvan Soos Date: Thu, 23 Jul 2026 14:34:46 +0200 Subject: [PATCH] Support 'allowElements' to allow custom tag names. --- sanitize_html/CHANGELOG.md | 3 +++ sanitize_html/lib/sanitize_html.dart | 4 ++++ sanitize_html/lib/src/sane_html_validator.dart | 5 ++++- sanitize_html/pubspec.yaml | 2 +- sanitize_html/test/sanitize_html_test.dart | 10 ++++++++++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/sanitize_html/CHANGELOG.md b/sanitize_html/CHANGELOG.md index fe76922e..f16e3288 100644 --- a/sanitize_html/CHANGELOG.md +++ b/sanitize_html/CHANGELOG.md @@ -1,3 +1,6 @@ +## v2.3.0 + * Support `allowElements` to allow custom tag names. + ## v2.2.0 * Allow the `` and `` elements, with `srcset` on ``, matching the [`html-pipeline` allowlist](https://github.com/gjtorikian/html-pipeline/blob/main/lib/html_pipeline/sanitization_filter.rb) diff --git a/sanitize_html/lib/sanitize_html.dart b/sanitize_html/lib/sanitize_html.dart index 3d6adc4b..d191b687 100644 --- a/sanitize_html/lib/sanitize_html.dart +++ b/sanitize_html/lib/sanitize_html.dart @@ -26,6 +26,8 @@ import 'src/sane_html_validator.dart' show SaneHtmlValidator; /// guard against XSS as this sanitizer also attempts to prevent the sanitized /// HTML from interfering with the page it is injected into. /// +/// The optional [allowElements] callback will be called with uppercase tag names. +/// /// For example, while it is possible to allow many CSS properties, this /// sanitizer does not allow any CSS. This creates a sanitizer that is easy to /// validate and is usually fine when sanitizing HTML from rendered markdown. @@ -71,11 +73,13 @@ import 'src/sane_html_validator.dart' show SaneHtmlValidator; /// [3]: https://support.google.com/webmasters/answer/81749 String sanitizeHtml( String htmlString, { + bool Function(String)? allowElements, bool Function(String)? allowElementId, bool Function(String)? allowClassName, Iterable? Function(String)? addLinkRel, }) { return SaneHtmlValidator( + allowElements: allowElements, allowElementId: allowElementId, allowClassName: allowClassName, addLinkRel: addLinkRel, diff --git a/sanitize_html/lib/src/sane_html_validator.dart b/sanitize_html/lib/src/sane_html_validator.dart index f0719632..6649eec9 100644 --- a/sanitize_html/lib/src/sane_html_validator.dart +++ b/sanitize_html/lib/src/sane_html_validator.dart @@ -237,11 +237,13 @@ final _elementAttributeValidators = /// /// [1]: https://github.com/gjtorikian/html-pipeline/blob/main/lib/html_pipeline/sanitization_filter.rb class SaneHtmlValidator { + final bool Function(String)? allowElements; final bool Function(String)? allowElementId; final bool Function(String)? allowClassName; final Iterable? Function(String)? addLinkRel; SaneHtmlValidator({ + required this.allowElements, required this.allowElementId, required this.allowClassName, required this.addLinkRel, @@ -256,7 +258,8 @@ class SaneHtmlValidator { void _sanitize(Node node) { if (node is Element) { final tagName = node.localName!.toUpperCase(); - if (!_allowedElements.contains(tagName)) { + if (!_allowedElements.contains(tagName) && + !(allowElements?.call(tagName) ?? false)) { node.remove(); return; } diff --git a/sanitize_html/pubspec.yaml b/sanitize_html/pubspec.yaml index abf77efd..f6d20958 100644 --- a/sanitize_html/pubspec.yaml +++ b/sanitize_html/pubspec.yaml @@ -1,5 +1,5 @@ name: sanitize_html -version: 2.2.0 +version: 2.3.0 description: >- Function for sanitizing HTML to prevent XSS by restrict elements and attributes to a safe subset of allowed values. diff --git a/sanitize_html/test/sanitize_html_test.dart b/sanitize_html/test/sanitize_html_test.dart index c29e3f23..bf7cfddc 100644 --- a/sanitize_html/test/sanitize_html_test.dart +++ b/sanitize_html/test/sanitize_html_test.dart @@ -29,6 +29,7 @@ void main() { return sanitizeHtml( template, + allowElements: (tagName) => tagName == 'ONLY-ALLOWED-TAG', allowElementId: (id) => id == 'only-allowed-id', allowClassName: (className) => className == 'only-allowed-class', addLinkRel: (href) => href == 'bad-link' ? ['ugc', 'nofollow'] : null, @@ -65,6 +66,13 @@ void main() { testContains('

hello', '

'); testContains('

hello', '

'); + // test tag name filtering + testContains( + 'hello', ''); + testContains('hello', 'hello'); + testNotContains('hello', 'disallowed-tag'); + testNotContains('hello', 'hello'); + // test id filtering.. testContains('hello', 'id'); testContains('hello', 'only-allowed-id'); @@ -185,6 +193,8 @@ void main() { withOptionalConfiguration: false); testNotContains('hey', 'rel=', withOptionalConfiguration: false); + testNotContains('hello', 'hello', + withOptionalConfiguration: false); testNotContains('hello', 'id=', withOptionalConfiguration: false); testNotContains('hello', 'class=',