Añade foro de comunidad integrado (app forum) con el diseño de NovaWoW
- 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>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
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())
|
||||
Reference in New Issue
Block a user