Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/turbohtml/clean/_sanitizer.py: 95%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

109 statements  

1""" 

2Sanitize untrusted HTML against an allowlist, the way bleach.clean did. 

3 

4bleach is the last maintained Python sanitizer and it is end of life, deprecated for sitting on the unmaintained 

5html5lib; turbohtml owns a maintained WHATWG tree builder, so this rebuilds the sanitizer on it. The model is the one 

6every safe sanitizer converged on: parse the input into a real tree, drop everything not on the allowlist while walking 

7it, and serialize once. There is no serialize-then-reparse round trip, which is the step that lets mutation XSS slip 

8back in, and a :class:`Policy` baseline removes ``<script>``, event-handler attributes, and ``javascript:`` URLs even 

9when a caller's allowlist would admit them, so a no-argument :func:`sanitize` is safe by construction. 

10 

11This module is the policy facade only; the walk that keeps, escapes, strips, or removes each node runs in C 

12(``turbohtml._html._sanitize``), so the safety baseline lives below the configurable surface. 

13""" 

14 

15from __future__ import annotations 

16 

17from dataclasses import dataclass, field 

18from enum import Enum 

19from itertools import starmap 

20from types import MappingProxyType 

21from typing import TYPE_CHECKING, Final, TypeVar, overload 

22 

23from turbohtml._html import _sanitize, _sanitize_policy 

24 

25if TYPE_CHECKING: 

26 import re 

27 from collections.abc import Callable, Mapping, Sequence 

28 

29 from turbohtml._html import Node 

30 

31 

32_NODE_T = TypeVar("_NODE_T", bound="Node") 

33 

34 

35@dataclass(frozen=True, slots=True) 

36class Removed: 

37 """ 

38 One item a policy dropped, the shape :meth:`Sanitizer.sanitize_report` returns. 

39 

40 :param tag: the tag name of the dropped element, or of the element a dropped attribute was on. 

41 :param attribute: the dropped attribute's name, or ``None`` when the element itself was dropped. 

42 """ 

43 

44 tag: str 

45 attribute: str | None = None 

46 

47 

48@dataclass(frozen=True, slots=True) 

49class Transform: 

50 """ 

51 A rename rule for ``Policy.transform_tags``, sanitize-html's ``simpleTransform``. 

52 

53 Mapping a source tag to a bare string renames it; mapping it to a ``Transform`` renames it and adds attributes. The 

54 added attributes are not trusted: they join the element's own attributes and go through the same allowlist and 

55 URL/style scrubbing, so a transform cannot add an attribute the policy would otherwise strip. 

56 

57 :param tag: the target tag name to rename to. 

58 :param attributes: attribute name/value pairs to add to the renamed element (added if absent, overwritten if 

59 present). 

60 """ 

61 

62 tag: str 

63 attributes: Mapping[str, str] = field(default_factory=dict) 

64 

65 

66class OnDisallowed(Enum): 

67 """ 

68 What to do with an element the policy does not allow. 

69 

70 ``ESCAPE`` renders the tag as visible text (``<x>`` becomes ``&lt;x&gt;``), the safe bleach default that keeps the 

71 content readable; ``STRIP`` drops the tag but keeps its children; ``REMOVE`` drops the tag and its whole subtree. 

72 """ 

73 

74 ESCAPE = 0 

75 STRIP = 1 

76 REMOVE = 2 

77 

78 

79#: bleach's default allowed tags, the migration baseline. 

80DEFAULT_TAGS: Final = frozenset({ 

81 "a", 

82 "abbr", 

83 "acronym", 

84 "b", 

85 "blockquote", 

86 "code", 

87 "em", 

88 "i", 

89 "li", 

90 "ol", 

91 "strong", 

92 "ul", 

93}) 

94#: bleach's default allowed attributes, keyed by tag (``"*"`` would match every tag). 

95DEFAULT_ATTRIBUTES: Final[Mapping[str, frozenset[str]]] = MappingProxyType({ 

96 "a": frozenset({"href", "title"}), 

97 "abbr": frozenset({"title"}), 

98 "acronym": frozenset({"title"}), 

99}) 

100#: bleach's default URL schemes. 

101DEFAULT_SCHEMES: Final = frozenset({"http", "https", "mailto"}) 

102#: bleach's default allowed CSS properties (CSS 2.1 safe set plus SVG paint), for scrubbing a ``style`` attribute. 

103DEFAULT_CSS_PROPERTIES: Final = frozenset({ 

104 "azimuth", "background-color", "border-bottom-color", "border-collapse", "border-color", "border-left-color", 

105 "border-right-color", "border-top-color", "clear", "color", "cursor", "direction", "display", "elevation", 

106 "float", "font", "font-family", "font-size", "font-style", "font-variant", "font-weight", "height", 

107 "letter-spacing", "line-height", "overflow", "pause", "pause-after", "pause-before", "pitch", "pitch-range", 

108 "richness", "speak", "speak-header", "speak-numeral", "speak-punctuation", "speech-rate", "stress", "text-align", 

109 "text-decoration", "text-indent", "unicode-bidi", "vertical-align", "voice-family", "volume", "white-space", 

110 "width", "fill", "fill-opacity", "fill-rule", "stroke", "stroke-width", "stroke-linecap", "stroke-linejoin", 

111 "stroke-opacity", 

112}) # fmt: skip 

113 

114# A roomier set for the relaxed preset: typical user-generated content (headings, tables, images, figures). 

115_RELAXED_TAGS: Final = DEFAULT_TAGS | { 

116 "p", "br", "hr", "span", "div", "pre", "h1", "h2", "h3", "h4", "h5", "h6", 

117 "dl", "dt", "dd", "sub", "sup", "del", "ins", "mark", "small", "u", "s", "q", "cite", "img", 

118 "figure", "figcaption", "table", "thead", "tbody", "tfoot", "tr", "th", "td", "caption", "colgroup", "col", 

119} # fmt: skip 

120_RELAXED_ATTRIBUTES: Final[Mapping[str, frozenset[str]]] = MappingProxyType({ 

121 "*": frozenset({"title", "lang", "dir"}), 

122 "a": frozenset({"href", "title", "name"}), 

123 "img": frozenset({"src", "alt", "title", "width", "height"}), 

124 "td": frozenset({"colspan", "rowspan"}), 

125 "th": frozenset({"colspan", "rowspan", "scope"}), 

126 "col": frozenset({"span"}), 

127 "colgroup": frozenset({"span"}), 

128}) 

129 

130 

131@dataclass(frozen=True) 

132class Policy: 

133 r""" 

134 An immutable, thread-safe description of what sanitizing keeps. 

135 

136 Build one and reuse it across threads. Whatever a policy allows, a non-configurable baseline still removes the 

137 unsafe element set, event-handler attributes, and dangerous URL schemes, so every policy is safe by construction. 

138 

139 :param tags: the allowed element set; any tag outside it is handled per ``on_disallowed_tag``. 

140 :param attributes: allowed attribute names keyed by tag (``"*"`` as the key matches every tag, and ``"*"`` inside a 

141 set allows every name). 

142 :param url_schemes: the allowlist for URL-bearing attributes such as ``href`` and ``src``, and for the redirect URL 

143 in a ``<meta http-equiv="refresh">`` ``content``; ``javascript:`` stays dropped even when listed. 

144 :param allow_relative_urls: keep relative (scheme-less) URLs, which carry no scheme to check. A value whose scheme 

145 holds a non-ASCII or control code point, such as ``ftp\xa0://``, is relative under the WHATWG URL parser. 

146 :param allow_fragment_urls: keep fragment-only URLs even when relative URLs are disallowed. 

147 :param on_disallowed_tag: how to treat a tag not in ``tags`` (:class:`OnDisallowed`: escape, strip, or remove). 

148 :param strip_comments: drop HTML comments from the output. 

149 :param add_link_rel: ``rel`` tokens forced onto every kept ``<a href>`` (e.g. ``noopener``). 

150 :param attribute_predicate: an optional predicate called once per attribute before allowlist and safety checks, 

151 in source order, including transform-added attributes. False drops an attribute; True cannot bypass safety. 

152 Attributes added by ``set_attributes`` do not pass through this predicate. 

153 :param attribute_filter: an optional rewrite for each surviving attribute, returning a replacement value or 

154 ``None`` to drop it. The replacement passes through the safety checks before serialization. 

155 :param set_attributes: attribute values forced onto every kept instance of a tag (added if absent, overwritten if 

156 present); unlike ``attribute_filter``, this can add attributes that were not there. Added values pass through 

157 the same URL, CSS, template, value, media-host, and named-property checks as parsed attributes. 

158 :param remove_with_content: disallowed tags whose whole subtree is dropped (e.g. ``script``/``style``) rather than 

159 escaped or stripped, so their text never leaks into the output. 

160 :param css_properties: the CSS property allowlist. A kept ``style`` attribute and, when ``style`` is in ``tags``, 

161 the ``<style>`` element's stylesheet body are both scrubbed against it: any declaration whose property name is 

162 not in the set (or whose value smuggles ``expression()`` or a ``url()`` with a disallowed scheme) is dropped. 

163 The non-configurable baseline also drops ``behavior`` and ``-moz-binding``. CSS escapes are decoded before 

164 these checks, while function-like text inside strings and comments remains inert. Selectors and block nesting 

165 remain. 

166 :param attribute_prefixes: allow any attribute whose name starts with one of these prefixes (e.g. ``"data-"`` for 

167 every ``data-*``), on top of the exact-name and ``"*"`` matches in ``attributes``. 

168 :param attribute_values: restrict a kept attribute to literal values, keyed ``{tag: {attribute: allowed_values}}``; 

169 a surviving attribute whose value is outside its set is dropped. This narrows an attribute ``attributes`` 

170 already admits and cannot admit a new one. 

171 :param transform_tags: rename rules applied before the allowlist, keyed by source tag (sanitize-html's 

172 ``transformTags``). A value that is a plain string renames the element to it; a :class:`Transform` renames it 

173 and adds attributes. The rename runs first, then the renamed element is re-checked from scratch, so a transform 

174 decides an element's *name* but never its safety: the allowlist still governs the target tag (mapping to 

175 ``script`` still drops it), and any added attributes are scrubbed like the element's own. Only HTML elements are 

176 transformed, matched by tag name. Empty (the default) renames nothing. 

177 :param allowed_styles: a per-property value allowlist for the ``style`` attribute, keyed 

178 ``{tag: {property: [pattern, ...]}}`` with ``"*"`` as a tag matching every element (sanitize-html's 

179 ``allowedStyles``). A declaration survives only when its property is listed for the element's tag or ``"*"`` 

180 (their pattern lists union) and its value matches one of the patterns (an unanchored :func:`re.search`). 

181 Patterns are strings or precompiled :class:`re.Pattern`. This narrows on top of ``css_properties`` and the 

182 non-configurable dangerous-value baseline: a property this admits is still dropped unless ``css_properties`` 

183 lists it too, and ``expression()`` or a ``url()`` with a disallowed scheme is dropped even if a pattern would 

184 match it. Empty (the default) leaves ``css_properties`` as the only ``style`` filter. 

185 :param media_hosts: allowed hosts for an embedded-media ``src`` (``audio``, ``video``, ``source``, ``track``); a 

186 ``src`` whose URL host is not one of these lowercase entries is dropped. Empty means no host restriction. 

187 :param strip_template_markers: collapse template-engine expressions (``{{ }}``, ``${ }``, ``<% %>``) in kept text 

188 and attribute values to a single space, so sanitized output cannot re-inject when a template engine (Angular, 

189 Vue, Mustache, EJS, ERB) later renders it. DOMPurify's ``SAFE_FOR_TEMPLATES``; off by default. 

190 :param isolate_named_props: prefix every kept ``id`` and ``name`` value with ``user-content-`` so it cannot shadow 

191 a built-in ``document`` or form property through named access (DOM clobbering: ``<input name="attributes">`` 

192 makes ``form.attributes`` resolve to the input, ``<img name="body">`` hides ``document.body``). The prefix is 

193 left alone when already present, so re-sanitizing is a fixpoint. DOMPurify's ``SANITIZE_NAMED_PROPS``; off by 

194 default. 

195 :param custom_element_check: a predicate that keeps an unlisted custom element (a hyphenated HTML tag such as 

196 ``my-widget`` or ``x-card``) without naming it in ``tags``, DOMPurify's 

197 ``CUSTOM_ELEMENT_HANDLING.tagNameCheck``. Given the element's lowercased tag name, return true to keep it (pass 

198 ``re.compile(r"x-.*").search`` to drive it from a regex). Only basic custom-element names reach it (the reserved 

199 ``annotation-xml``/``font-face`` family never does), and the safety baseline still escapes unsafe tags and 

200 scrubs ``on*``/URL/style attributes on whatever it keeps. ``None`` (the default) escapes every unlisted tag as 

201 before. 

202 :param custom_attribute_check: a predicate that keeps an unlisted attribute on a kept custom element, DOMPurify's 

203 ``CUSTOM_ELEMENT_HANDLING.attributeNameCheck``. Given ``(tag, attribute_name)`` return true to keep it; the 

204 ``on*``, URL-scheme, and ``style`` scrubbing still apply, so this widens the name allowlist, never the safety 

205 baseline. ``None`` (the default) keeps only the attributes ``attributes`` already admits. 

206 :param allow_customized_builtins: keep an ``is`` attribute whose value passes ``custom_element_check``, so a 

207 customized built-in element (``<button is="my-button">``) survives, DOMPurify's 

208 ``allowCustomizedBuiltInElements``. Off by default, and inert without ``custom_element_check``. 

209 :param allow_html: keep HTML-namespace elements; turning it off drops the whole HTML namespace, DOMPurify's 

210 ``USE_PROFILES.html``. Independent of the tag allowlist, so it composes with ``allow_svg``/``allow_mathml`` to 

211 select which content languages a policy admits. 

212 :param allow_svg: keep SVG-namespace elements, DOMPurify's ``USE_PROFILES.svg``. Off drops every SVG element even 

213 when its tag is in ``tags``. The baseline rejects SVG animation elements because they can assign event handlers 

214 or script URLs at runtime; an allowlist cannot keep them. 

215 :param allow_mathml: keep MathML-namespace elements, DOMPurify's ``USE_PROFILES.mathMl``. Off drops every MathML 

216 element even when its tag is in ``tags``. 

217 :param xml: emit well-formed XML/XHTML instead of HTML, DOMPurify's ``RETURN_DOM`` served through the XML 

218 serializer. Every kept empty element self-closes (``<br/>``), foreign SVG and MathML subtrees carry their 

219 namespace declarations, text and attribute values follow the XML escaping rules, and a kept comment or a 

220 stray control character is neutralized so the result reparses through :func:`turbohtml.parse_xml`. Use it to 

221 clean an XHTML dialect (Reportlab's RML, ePub content) whose consumer rejects HTML's bare ``<br>``. Off (the 

222 default) keeps the HTML serialization. 

223 """ 

224 

225 tags: frozenset[str] = DEFAULT_TAGS 

226 # default_factory, not a plain default: dataclasses before 3.12 reject a MappingProxyType default as "mutable" 

227 attributes: Mapping[str, frozenset[str]] = field(default_factory=lambda: DEFAULT_ATTRIBUTES) 

228 url_schemes: frozenset[str] = DEFAULT_SCHEMES 

229 allow_relative_urls: bool = True 

230 allow_fragment_urls: bool = False 

231 on_disallowed_tag: OnDisallowed = OnDisallowed.ESCAPE 

232 strip_comments: bool = True 

233 add_link_rel: frozenset[str] = frozenset() 

234 attribute_filter: Callable[[str, str, str], str | None] | None = None 

235 set_attributes: Mapping[str, Mapping[str, str]] = field(default_factory=dict) 

236 remove_with_content: frozenset[str] = frozenset() 

237 css_properties: frozenset[str] = DEFAULT_CSS_PROPERTIES 

238 attribute_prefixes: frozenset[str] = frozenset() 

239 attribute_values: Mapping[str, Mapping[str, frozenset[str]]] = field(default_factory=dict) 

240 media_hosts: frozenset[str] = frozenset() 

241 strip_template_markers: bool = False 

242 allowed_styles: Mapping[str, Mapping[str, Sequence[str | re.Pattern[str]]]] = field(default_factory=dict) 

243 transform_tags: Mapping[str, Transform | str] = field(default_factory=dict) 

244 isolate_named_props: bool = False 

245 custom_element_check: Callable[[str], bool] | None = None 

246 custom_attribute_check: Callable[[str, str], bool] | None = None 

247 allow_customized_builtins: bool = False 

248 allow_html: bool = True 

249 allow_svg: bool = True 

250 allow_mathml: bool = True 

251 xml: bool = False 

252 attribute_predicate: Callable[[str, str, str], bool] | None = None 

253 _bleach_url_policy: bool = False 

254 

255 @classmethod 

256 def strict(cls) -> Policy: 

257 """ 

258 Allow no markup at all: every tag is escaped to text and every attribute dropped. 

259 

260 :returns: the strict policy. 

261 """ 

262 return cls(tags=frozenset(), attributes=MappingProxyType({})) 

263 

264 @classmethod 

265 def basic(cls) -> Policy: 

266 """ 

267 Allow bleach's default 12-tag set, for migration parity. 

268 

269 :returns: the basic policy. 

270 """ 

271 return cls() 

272 

273 @classmethod 

274 def relaxed(cls) -> Policy: 

275 """ 

276 Allow the richer set typical user-generated content needs: headings, tables, images, and figures. 

277 

278 :returns: the relaxed policy. 

279 """ 

280 return cls( 

281 tags=frozenset(_RELAXED_TAGS), 

282 attributes=_RELAXED_ATTRIBUTES, 

283 add_link_rel=frozenset({"noopener", "noreferrer"}), 

284 ) 

285 

286 

287class Sanitizer: 

288 """ 

289 A reusable sanitizer; build it once from a :class:`Policy` and call :meth:`sanitize` from any thread. 

290 

291 :param options: the policy to enforce; None uses bleach's default allowlist. 

292 """ 

293 

294 def __init__(self, options: Policy | None = None) -> None: 

295 """Compile a policy into the form the C walk consumes.""" 

296 self.policy = options if options is not None else Policy() 

297 ( 

298 self._attributes, 

299 self._link_rel, 

300 self._set_attributes, 

301 self._attribute_values, 

302 self._allowed_styles, 

303 self._transform_tags, 

304 ) = _sanitize_policy( 

305 self.policy.attributes, 

306 self.policy.add_link_rel, 

307 self.policy.set_attributes, 

308 self.policy.attribute_values, 

309 self.policy.allowed_styles, 

310 self.policy.transform_tags, 

311 Transform, 

312 ) 

313 

314 def sanitize(self, html: str | Node) -> str: 

315 """ 

316 Sanitize an HTML fragment. 

317 

318 :param html: the untrusted HTML fragment, or an already parsed node whose subtree to sanitize (see 

319 :meth:`sanitize_node`), which skips the parse. 

320 :returns: the sanitized, safe HTML. 

321 :raises TypeError: if a set-typed policy field (``tags``, ``url_schemes``, ``remove_with_content``, 

322 ``css_properties``, ``attribute_prefixes``, ``media_hosts``) holds a value that is not a set or frozenset, 

323 or ``attribute_prefixes`` contains a non-string. 

324 :raises ValueError: if ``attribute_prefixes`` contains an empty string, which would match every attribute. 

325 """ 

326 return self._render(self._filter(html, None)) 

327 

328 def sanitize_report(self, html: str | Node) -> tuple[str, list[Removed]]: 

329 """ 

330 Sanitize a fragment and report what the policy dropped, the way DOMPurify populates ``DOMPurify.removed``. 

331 

332 Every disallowed element (removed, stripped, or escaped) and every stripped attribute becomes one 

333 :class:`Removed` record, in the order the walk reached it, so a caller can log or tune a policy against 

334 evidence instead of guessing. 

335 

336 :param html: the untrusted HTML fragment, or an already parsed node (see :meth:`sanitize_node`). 

337 :returns: the sanitized HTML paired with the list of dropped items. 

338 :raises TypeError: like :meth:`sanitize`, on a mistyped set-valued policy field. 

339 :raises ValueError: like :meth:`sanitize`, on an empty ``attribute_prefixes`` entry. 

340 """ 

341 removed: list[tuple[str, str | None]] = [] 

342 html_out = self._render(self._filter(html, removed)) 

343 return html_out, list(starmap(Removed, removed)) 

344 

345 def sanitize_node(self, node: _NODE_T) -> _NODE_T: 

346 """ 

347 Sanitize an already parsed subtree and return the sanitized copy as a tree. 

348 

349 This is the tree-to-tree form for a pipeline that parses once, runs several transforms, and serializes once, 

350 the way DOMPurify's ``RETURN_DOM`` hands back a DOM instead of a string. The node itself is the kept context, 

351 like the fragment root of :meth:`sanitize`: the policy applies to its descendants, never to the node. So pass 

352 the ``body`` of a document to clean what it holds, or a ``Document`` to have the policy judge the ``html`` 

353 element itself (a ``bleach``-style allowlist then needs ``html``, ``head`` and ``body`` listed). The copy lives 

354 in its own tree and inherits the source's XML flag; the source is left untouched. 

355 

356 :param node: the element or document whose subtree to sanitize. 

357 :returns: a new node of the same kind holding the sanitized subtree. 

358 :raises TypeError: if ``node`` is not a node, or on a mistyped set-valued policy field. 

359 :raises ValueError: like :meth:`sanitize`, on an empty ``attribute_prefixes`` entry. 

360 """ 

361 return self._filter(_node_only(node), None) 

362 

363 def sanitize_report_node(self, node: _NODE_T) -> tuple[_NODE_T, list[Removed]]: 

364 """ 

365 Sanitize an already parsed subtree and report what the policy dropped. 

366 

367 :meth:`sanitize_node` and :meth:`sanitize_report` combined: the copy comes back as a tree, the drops as 

368 records. 

369 

370 :param node: the element or document whose subtree to sanitize. 

371 :returns: the sanitized copy paired with one :class:`Removed` record per dropped element or attribute. 

372 :raises TypeError: if ``node`` is not a node, or on a mistyped set-valued policy field. 

373 :raises ValueError: like :meth:`sanitize`, on an empty ``attribute_prefixes`` entry. 

374 """ 

375 removed: list[tuple[str, str | None]] = [] 

376 copy = self._filter(_node_only(node), removed) 

377 return copy, list(starmap(Removed, removed)) 

378 

379 def _render(self, root: Node) -> str: 

380 """Serialize the sanitized root's children as XML when the policy asks, else as HTML.""" 

381 return root.inner_xml if self.policy.xml else root.inner_html 

382 

383 @overload 

384 def _filter(self, html: _NODE_T, removed: list[tuple[str, str | None]] | None) -> _NODE_T: ... 

385 

386 @overload 

387 def _filter(self, html: str, removed: list[tuple[str, str | None]] | None) -> Node: ... 

388 

389 def _filter(self, html: str | Node, removed: list[tuple[str, str | None]] | None) -> Node: 

390 """Run the C walk over a freshly parsed fragment or a copy of a node, appending drops to ``removed``.""" 

391 policy = self.policy 

392 return _sanitize( 

393 html, 

394 policy.tags, 

395 self._attributes, 

396 policy.url_schemes, 

397 policy.allow_relative_urls, 

398 policy.allow_fragment_urls, 

399 policy.on_disallowed_tag.value, 

400 policy.strip_comments, 

401 self._link_rel, 

402 policy.attribute_filter, 

403 self._set_attributes, 

404 policy.remove_with_content, 

405 policy.css_properties, 

406 policy.attribute_prefixes, 

407 self._attribute_values, 

408 policy.media_hosts, 

409 policy.strip_template_markers, 

410 removed, 

411 self._allowed_styles, 

412 self._transform_tags, 

413 policy.isolate_named_props, 

414 policy.custom_element_check, 

415 policy.custom_attribute_check, 

416 policy.allow_customized_builtins, 

417 policy.allow_html, 

418 policy.allow_svg, 

419 policy.allow_mathml, 

420 policy.attribute_predicate, 

421 policy._bleach_url_policy, # ruff:ignore[private-member-access] # bridge the migration-only C option 

422 policy.xml, # the XML serializer round-trips through parse_xml, so the HTML parse-stability pass is skipped 

423 ) 

424 

425 

426def _node_only(node: _NODE_T) -> _NODE_T: 

427 """Reject a str where a parsed node is required, so a caller who meant the string form gets told which one.""" 

428 if isinstance(node, str): 

429 msg = "sanitize_node takes a parsed node; pass a str to sanitize instead" 

430 raise TypeError(msg) 

431 return node 

432 

433 

434def sanitize(html: str | Node, options: Policy | None = None) -> str: 

435 """ 

436 Sanitize an HTML fragment against a policy. 

437 

438 :param html: the untrusted HTML fragment, or an already parsed node whose subtree to sanitize (see 

439 :func:`sanitize_node`), which skips the parse. 

440 :param options: the policy to enforce; None uses bleach's default allowlist. 

441 :returns: the sanitized, safe HTML. 

442 :raises TypeError: if a set-typed policy field (``tags``, ``url_schemes``, ``remove_with_content``, 

443 ``css_properties``, ``attribute_prefixes``, ``media_hosts``) holds a value that is not a set or frozenset, or 

444 ``attribute_prefixes`` contains a non-string. 

445 :raises ValueError: if ``attribute_prefixes`` contains an empty string, which would match every attribute. 

446 """ 

447 return Sanitizer(options).sanitize(html) 

448 

449 

450def sanitize_report(html: str | Node, options: Policy | None = None) -> tuple[str, list[Removed]]: 

451 """ 

452 Sanitize a fragment and report what the policy dropped, like DOMPurify's ``DOMPurify.removed``. 

453 

454 :param html: the untrusted HTML fragment, or an already parsed node (see :func:`sanitize_node`). 

455 :param options: the policy to enforce; None uses bleach's default allowlist. 

456 :returns: the sanitized HTML paired with one :class:`Removed` record per dropped element or attribute. 

457 :raises TypeError: like :func:`sanitize`, on a mistyped set-valued policy field. 

458 :raises ValueError: like :func:`sanitize`, on an empty ``attribute_prefixes`` entry. 

459 """ 

460 return Sanitizer(options).sanitize_report(html) 

461 

462 

463def sanitize_node(node: _NODE_T, options: Policy | None = None) -> _NODE_T: 

464 """ 

465 Sanitize an already parsed subtree against a policy and return the sanitized copy as a tree. 

466 

467 The tree-to-tree form for a pipeline that parses once and serializes once; see 

468 :meth:`Sanitizer.sanitize_node` for what the node means and what comes back. 

469 

470 :param node: the element or document whose subtree to sanitize. 

471 :param options: the policy to enforce; None uses bleach's default allowlist. 

472 :returns: a new node of the same kind holding the sanitized subtree. 

473 :raises TypeError: if ``node`` is not a node, or on a mistyped set-valued policy field. 

474 :raises ValueError: like :func:`sanitize`, on an empty ``attribute_prefixes`` entry. 

475 """ 

476 return Sanitizer(options).sanitize_node(node) 

477 

478 

479def sanitize_report_node(node: _NODE_T, options: Policy | None = None) -> tuple[_NODE_T, list[Removed]]: 

480 """ 

481 Sanitize an already parsed subtree and report what the policy dropped. 

482 

483 :param node: the element or document whose subtree to sanitize. 

484 :param options: the policy to enforce; None uses bleach's default allowlist. 

485 :returns: the sanitized copy paired with one :class:`Removed` record per dropped element or attribute. 

486 :raises TypeError: if ``node`` is not a node, or on a mistyped set-valued policy field. 

487 :raises ValueError: like :func:`sanitize`, on an empty ``attribute_prefixes`` entry. 

488 """ 

489 return Sanitizer(options).sanitize_report_node(node) 

490 

491 

492__all__ = [ 

493 "DEFAULT_ATTRIBUTES", 

494 "DEFAULT_CSS_PROPERTIES", 

495 "DEFAULT_SCHEMES", 

496 "DEFAULT_TAGS", 

497 "OnDisallowed", 

498 "Policy", 

499 "Removed", 

500 "Sanitizer", 

501 "Transform", 

502 "sanitize", 

503 "sanitize_node", 

504 "sanitize_report", 

505 "sanitize_report_node", 

506]