83 lines
2.5 KiB
Python
83 lines
2.5 KiB
Python
"""HTML rendering helpers used only by the legacy Manager UI."""
|
|
import re
|
|
from html import escape, unescape
|
|
from html.entities import html5
|
|
|
|
|
|
SAFE_URL_SCHEMES = frozenset({'http', 'https'})
|
|
_URL_SCHEME_NOISE = re.compile(r'[\x00-\x20\x7f]')
|
|
_URL_HEAD_DELIMITERS = ('/', '?', '#')
|
|
|
|
|
|
def escape_html_attribute(value):
|
|
"""Escape a value for a quoted HTML attribute."""
|
|
return escape(str(value), quote=True)
|
|
|
|
|
|
_HTML_ENTITY = re.compile(r'&(#[0-9]+|#[xX][0-9a-fA-F]+|[A-Za-z][A-Za-z0-9]*);')
|
|
_HTML_TEXT_TOKEN = re.compile(_HTML_ENTITY.pattern + "|[&<>\"']")
|
|
|
|
|
|
def unescape_html_entities(text):
|
|
"""Decode complete entities once, leaving query keys such as ¬ebook intact."""
|
|
def replace(match):
|
|
entity = match.group(1)
|
|
if entity.startswith('#'):
|
|
return unescape(match.group(0))
|
|
return html5.get(entity + ';', match.group(0))
|
|
|
|
return _HTML_ENTITY.sub(replace, text)
|
|
|
|
|
|
def escape_html_text(value):
|
|
"""Escape HTML text without double-escaping existing entities."""
|
|
return _HTML_TEXT_TOKEN.sub(
|
|
lambda m: m.group(0) if m.group(1) else escape_html_attribute(m.group(0)),
|
|
str(value),
|
|
)
|
|
|
|
|
|
def sanitize_url(url):
|
|
"""Allow HTTP(S), relative URLs and anchors.
|
|
|
|
HTML callers must also escape the result as an attribute."""
|
|
raw = '' if url is None else str(url)
|
|
probe = _URL_SCHEME_NOISE.sub('', raw)
|
|
if not probe:
|
|
return '#'
|
|
|
|
cut = len(probe)
|
|
for delimiter in _URL_HEAD_DELIMITERS:
|
|
found = probe.find(delimiter)
|
|
if found != -1:
|
|
cut = min(cut, found)
|
|
head = probe[:cut]
|
|
|
|
if ':' in head:
|
|
return raw.strip() if head.split(':', 1)[0].lower() in SAFE_URL_SCHEMES else '#'
|
|
|
|
return raw.strip()
|
|
|
|
|
|
def sanitize_html_fragment(fragment):
|
|
"""Sanitize notice HTML, preserving its formatting and opening links safely."""
|
|
# Keep nh3 loading within the legacy notice path.
|
|
import nh3
|
|
|
|
return nh3.clean(
|
|
'' if fragment is None else str(fragment),
|
|
tags=nh3.ALLOWED_TAGS | {'font'},
|
|
attributes={
|
|
**nh3.ALLOWED_ATTRIBUTES,
|
|
'*': {'class', 'title', 'align', 'width', 'height'},
|
|
'font': {'color'},
|
|
},
|
|
# These elements previously hid their contents from the notice.
|
|
clean_content_tags={
|
|
'script', 'style', 'iframe', 'object', 'embed', 'svg', 'math',
|
|
'template', 'noscript', 'textarea', 'title',
|
|
},
|
|
url_schemes=SAFE_URL_SCHEMES,
|
|
set_tag_attribute_values={'a': {'target': '_blank'}},
|
|
)
|