1"""
2A compatible ``bleach.clean`` interface for projects migrating off bleach.
3
4This is a thin translator over :mod:`turbohtml.clean`, kept apart from the main API so the compatibility surface stays
5bounded. ``clean(text, tags=..., attributes=..., protocols=..., strip=...)`` keeps bleach's signature, including the
6list, per-tag-dict, and callable forms of ``attributes``. Kept attributes and text may change entity spelling after
7serialization. Event-handler attributes and ``javascript:`` URLs are dropped unconditionally, even when a permissive
8``attributes`` callable would keep them, because the underlying policy's safety baseline is fixed.
9"""
10
11from __future__ import annotations
12
13from collections.abc import Callable, Iterable, Mapping
14from typing import TypeAlias
15
16from turbohtml._html import _bleach_allow_relative, _bleach_attributes
17from turbohtml.clean import DEFAULT_ATTRIBUTES, DEFAULT_SCHEMES, DEFAULT_TAGS, OnDisallowed, Policy, sanitize
18
19#: bleach's default allowed tags, attributes, and protocols, under their bleach names.
20ALLOWED_TAGS = DEFAULT_TAGS
21ALLOWED_ATTRIBUTES = DEFAULT_ATTRIBUTES
22ALLOWED_PROTOCOLS = DEFAULT_SCHEMES
23
24_AttributeRules: TypeAlias = (
25 Iterable[str] | Mapping[str, Iterable[str] | Callable[[str, str, str], bool]] | Callable[[str, str, str], bool]
26)
27
28
29def clean( # ruff:ignore[too-many-arguments, too-many-positional-arguments] # this is bleach.clean's signature, kept verbatim for compatibility
30 text: str,
31 tags: Iterable[str] | None = None,
32 attributes: _AttributeRules | None = None,
33 protocols: Iterable[str] | None = None,
34 strip: bool = False, # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument] # bleach keeps strip a positional flag
35 strip_comments: bool = True, # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument] # bleach keeps strip_comments a positional flag
36 css_sanitizer: object = None,
37) -> str:
38 """
39 Sanitize HTML against a bleach-style allowlist, the way ``bleach.clean`` did.
40
41 :param text: the untrusted HTML.
42 :param tags: the allowed tag names; None uses bleach's default set.
43 :param attributes: the allowed attributes as a flat list, a per-tag dict, or a (tag, name, value) predicate;
44 None uses bleach's default.
45 :param protocols: the allowed URL schemes; None uses bleach's default.
46 :param strip: drop a disallowed tag and keep its children, rather than escaping it to text.
47 :param strip_comments: drop HTML comments from the output.
48 :param css_sanitizer: accepted for signature compatibility; passing one raises ``NotImplementedError``.
49 :returns: the sanitized, safe HTML.
50 """
51 if css_sanitizer is not None: # CSS sanitizing is a separate sub-problem, not yet ported
52 msg = "css_sanitizer is not implemented yet; drop the style attribute and <style> instead"
53 raise NotImplementedError(msg)
54 names, attribute_predicate = attribute_policy(ALLOWED_ATTRIBUTES if attributes is None else attributes)
55 if protocols is None:
56 policy = Policy(
57 tags=ALLOWED_TAGS if tags is None else frozenset(tags),
58 attributes=names,
59 url_schemes=ALLOWED_PROTOCOLS,
60 on_disallowed_tag=OnDisallowed.STRIP if strip else OnDisallowed.ESCAPE,
61 strip_comments=strip_comments,
62 attribute_predicate=attribute_predicate,
63 _bleach_url_policy=True,
64 )
65 else:
66 schemes = frozenset(protocols)
67 policy = Policy(
68 tags=ALLOWED_TAGS if tags is None else frozenset(tags),
69 attributes=names,
70 url_schemes=schemes,
71 allow_relative_urls=_bleach_allow_relative(schemes),
72 allow_fragment_urls=True,
73 on_disallowed_tag=OnDisallowed.STRIP if strip else OnDisallowed.ESCAPE,
74 strip_comments=strip_comments,
75 attribute_predicate=attribute_predicate,
76 _bleach_url_policy=True,
77 )
78 return sanitize(text, policy)
79
80
81def attribute_policy(
82 attributes: _AttributeRules,
83) -> tuple[dict[str, frozenset[str]], Callable[[str, str, str], bool] | None]:
84 """Keep bleach attribute rules when configuring a native ``Policy``."""
85 return _bleach_attributes(attributes, Mapping)
86
87
88__all__ = [
89 "ALLOWED_ATTRIBUTES",
90 "ALLOWED_PROTOCOLS",
91 "ALLOWED_TAGS",
92 "attribute_policy",
93 "clean",
94]