2c30844bf7
- app 'forum': portada (categorías/foros), ver foro y tema (paginado), crear tema, responder, editar/borrar posts y moderación (fijar, bloquear, mover, borrar/restaurar) - lee/escribe la BD acore_web (5ª conexión, DB_NAME_WEB) por SQL directo, tablas forum_categories/forums/forum_topics/forum_posts/forum_reads (portado de NovaWeb-main) - identidad = cuenta de juego en sesión; permisos simplificados por gmlevel (FORUM_MOD_GMLEVEL) en vez de la matriz group_level×permission del original - saneado del HTML de posts con nh3 (evita el XSS del original); POST+CSRF en formularios - plantillas con los partials del sitio; enlace 'FOROS' del menú apunta al foro interno - sql/forum_schema.sql y docs/FORO.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
53 lines
1.5 KiB
Python
53 lines
1.5 KiB
Python
"""
|
|
Saneado del HTML de los posts del foro (el editor del cliente puede enviar HTML).
|
|
|
|
El proyecto original guardaba el HTML sin sanear y lo renderizaba con |raw
|
|
(XSS almacenado). Aquí lo saneamos con nh3 (allowlist) al guardar, de modo que
|
|
la plantilla puede renderizarlo con |safe sin riesgo.
|
|
"""
|
|
|
|
import nh3
|
|
|
|
# Etiquetas permitidas (formato básico de foro)
|
|
_ALLOWED_TAGS = {
|
|
"p", "br", "hr", "span", "div",
|
|
"strong", "b", "em", "i", "u", "s", "strike", "sub", "sup",
|
|
"ul", "ol", "li", "blockquote", "code", "pre",
|
|
"h1", "h2", "h3", "h4", "h5", "h6",
|
|
"a", "img",
|
|
"table", "thead", "tbody", "tr", "th", "td",
|
|
}
|
|
|
|
_ALLOWED_ATTRIBUTES = {
|
|
"a": {"href", "title", "target"},
|
|
"img": {"src", "alt", "title", "width", "height"},
|
|
"span": {"style"},
|
|
"div": {"style"},
|
|
"td": {"colspan", "rowspan"},
|
|
"th": {"colspan", "rowspan"},
|
|
}
|
|
|
|
# Solo esquemas de URL seguros
|
|
_ALLOWED_URL_SCHEMES = {"http", "https", "mailto"}
|
|
|
|
|
|
def clean_post_html(html):
|
|
"""Devuelve el HTML saneado y seguro para renderizar con |safe."""
|
|
if not html:
|
|
return ""
|
|
return nh3.clean(
|
|
html,
|
|
tags=_ALLOWED_TAGS,
|
|
attributes=_ALLOWED_ATTRIBUTES,
|
|
url_schemes=_ALLOWED_URL_SCHEMES,
|
|
link_rel="noopener noreferrer nofollow",
|
|
)
|
|
|
|
|
|
def plain_length(html):
|
|
"""Longitud del texto plano (sin etiquetas), para validar longitud mínima."""
|
|
if not html:
|
|
return 0
|
|
text = nh3.clean(html, tags=set(), attributes={})
|
|
return len(text.strip())
|