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

package:sanitize_html - Added containsDisallowedContent Function for Detecting Prohibited HTML in User-Defined Validation Scenarios #263

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions sanitize_html/lib/sanitize_html.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,28 @@ String sanitizeHtml(
addLinkRel: addLinkRel,
).sanitize(htmlString);
}

/// Checks if [htmlString] contains any disallowed elements or attributes.
///
/// This function uses the same rules as [sanitizeHtml] but only verifies
/// compliance without modifying the HTML content. It returns `true` if any
/// disallowed elements or attributes are detected, otherwise `false`.
///
/// Example usage:
/// ```dart
/// bool hasDisallowedContent = containsDisallowedContent('<a href="javascript:alert()">link</a>');
/// // Returns true due to disallowed "javascript:" in href
/// ```
bool containsDisallowedContent(
String htmlString, {
bool Function(String)? allowElementId,
bool Function(String)? allowClassName,
Iterable<String>? Function(String)? addLinkRel,
}) {
return SaneHtmlValidator(
allowElementId: allowElementId,
allowClassName: allowClassName,
addLinkRel: addLinkRel,
).containsDisallowedContent(htmlString);
}

65 changes: 65 additions & 0 deletions sanitize_html/lib/src/sane_html_validator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,15 @@ class SaneHtmlValidator {
return root.outerHtml;
}

bool containsDisallowedContent(String htmlString) {
try {
final root = html_parser.parseFragment(htmlString);
return _hasDisallowedContent(root);
} catch (e) {
throw Exception('Error parsing HTML: $e');
}
}

void _sanitize(Node node) {
if (node is Element) {
final tagName = node.localName!.toUpperCase();
Expand Down Expand Up @@ -268,6 +277,62 @@ class SaneHtmlValidator {
}
}

bool _hasDisallowedContent(Node node) {
// Check if the node is an Element
if (node is Element) {
final tagName = node.localName?.toUpperCase() ?? '';

// If the tag is not allowed, return true
if (!_allowedElements.contains(tagName)) {
return true;
}

// Check each attribute of the element
for (var attribute in node.attributes.entries) {
final attrName = attribute.key.toString();
final attrValue = attribute.value;

// Allow 'id' attribute only if allowElementId allows it
if (attrName == 'id' && (allowElementId == null || !allowElementId!(attrValue))) {
return true;
}

// Allow 'class' attribute only if all classes are allowed
if (attrName == 'class' && allowClassName != null) {
for (var className in node.classes) {
if (!allowClassName!(className)) {
return true;
}
}
}

// Check other attributes against allowed attributes list and validators
if (!_isAttributeAllowed(tagName, attrName, attrValue)) {
return true;
}
}

// Special handling for 'A' elements with 'href' attribute
if (tagName == 'A' && node.attributes.containsKey('href')) {
final href = node.attributes['href'];
// Validate the href attribute
if (href != null && !_validLink(href)) {
return true; // Disallow invalid URLs in 'href'
}
}
}

// Recursively check child nodes for disallowed content
for (var child in node.nodes) {
if (_hasDisallowedContent(child)) {
return true;
}
}

// If no disallowed content was found, return false
return false;
}

bool _isAttributeAllowed(String tagName, String attrName, String value) {
if (_alwaysAllowedAttributes.contains(attrName)) return true;

Expand Down