refactor
This commit is contained in:
parent
2ffc414444
commit
bee7213430
97
profiles.py
97
profiles.py
@ -3,10 +3,8 @@ import json
|
|||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import markdown
|
import markdown
|
||||||
|
from html.parser import HTMLParser
|
||||||
from sanitizer import sanitize_html_fragment
|
|
||||||
from db import get_db
|
from db import get_db
|
||||||
|
|
||||||
DEFAULT_ROLE_NAME_COLORS = {
|
DEFAULT_ROLE_NAME_COLORS = {
|
||||||
@ -14,6 +12,99 @@ DEFAULT_ROLE_NAME_COLORS = {
|
|||||||
"moderator": "#8ab4ff",
|
"moderator": "#8ab4ff",
|
||||||
"user": "#f5f5f7",
|
"user": "#f5f5f7",
|
||||||
}
|
}
|
||||||
|
_ALLOWED_TAGS = {
|
||||||
|
"b",
|
||||||
|
"strong",
|
||||||
|
"i",
|
||||||
|
"em",
|
||||||
|
"u",
|
||||||
|
"p",
|
||||||
|
"br",
|
||||||
|
"ul",
|
||||||
|
"ol",
|
||||||
|
"li",
|
||||||
|
"blockquote",
|
||||||
|
"code",
|
||||||
|
"pre",
|
||||||
|
"a",
|
||||||
|
"h1",
|
||||||
|
"h2",
|
||||||
|
"h3",
|
||||||
|
"h4",
|
||||||
|
"h5",
|
||||||
|
"h6",
|
||||||
|
}
|
||||||
|
|
||||||
|
_ALLOWED_ATTRS = {
|
||||||
|
"a": {"href", "title", "target", "rel"},
|
||||||
|
}
|
||||||
|
|
||||||
|
_ALLOWED_SCHEMES = ("http://", "https://", "mailto:")
|
||||||
|
|
||||||
|
|
||||||
|
def _escape_attr(value: str) -> str:
|
||||||
|
return html.escape(value, quote=True)
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_html_fragment(raw: str) -> str:
|
||||||
|
class SafeHtmlParser(HTMLParser):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(convert_charrefs=True)
|
||||||
|
self.parts = []
|
||||||
|
|
||||||
|
def handle_starttag(self, tag, attrs):
|
||||||
|
tag = tag.lower()
|
||||||
|
if tag not in _ALLOWED_TAGS:
|
||||||
|
return
|
||||||
|
|
||||||
|
allowed = _ALLOWED_ATTRS.get(tag, set())
|
||||||
|
clean_attrs = []
|
||||||
|
|
||||||
|
for key, value in attrs:
|
||||||
|
key = (key or "").lower()
|
||||||
|
value = value or ""
|
||||||
|
|
||||||
|
if key not in allowed:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if tag == "a" and key == "href":
|
||||||
|
lowered = value.strip().lower()
|
||||||
|
if not lowered.startswith(_ALLOWED_SCHEMES):
|
||||||
|
continue
|
||||||
|
|
||||||
|
clean_attrs.append((key, value))
|
||||||
|
|
||||||
|
if tag == "a":
|
||||||
|
attr_keys = {key for key, _ in clean_attrs}
|
||||||
|
if "target" not in attr_keys:
|
||||||
|
clean_attrs.append(("target", "_blank"))
|
||||||
|
if "rel" not in attr_keys:
|
||||||
|
clean_attrs.append(("rel", "noopener noreferrer"))
|
||||||
|
|
||||||
|
attr_text = "".join(
|
||||||
|
f' {key}="{_escape_attr(value)}"'
|
||||||
|
for key, value in clean_attrs
|
||||||
|
)
|
||||||
|
self.parts.append(f"<{tag}{attr_text}>")
|
||||||
|
|
||||||
|
def handle_endtag(self, tag):
|
||||||
|
tag = tag.lower()
|
||||||
|
if tag in _ALLOWED_TAGS and tag not in {"br"}:
|
||||||
|
self.parts.append(f"</{tag}>")
|
||||||
|
|
||||||
|
def handle_data(self, data):
|
||||||
|
self.parts.append(html.escape(data))
|
||||||
|
|
||||||
|
def handle_entityref(self, name):
|
||||||
|
self.parts.append(f"&{name};")
|
||||||
|
|
||||||
|
def handle_charref(self, name):
|
||||||
|
self.parts.append(f"&#{name};")
|
||||||
|
|
||||||
|
parser = SafeHtmlParser()
|
||||||
|
parser.feed(raw or "")
|
||||||
|
parser.close()
|
||||||
|
return "".join(parser.parts)
|
||||||
|
|
||||||
PROFILE_BIO_MODES = {"txt", "md", "html"}
|
PROFILE_BIO_MODES = {"txt", "md", "html"}
|
||||||
THEME_MODES = {"dark", "light"}
|
THEME_MODES = {"dark", "light"}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ from .common import (
|
|||||||
now_iso,
|
now_iso,
|
||||||
csrf_valid,
|
csrf_valid,
|
||||||
rate_limited,
|
rate_limited,
|
||||||
|
current_user,
|
||||||
require_user,
|
require_user,
|
||||||
require_writable_user,
|
require_writable_user,
|
||||||
normalize_username,
|
normalize_username,
|
||||||
@ -30,7 +31,6 @@ from .common import (
|
|||||||
normalized_hex_color,
|
normalized_hex_color,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def register_profile_routes(app):
|
def register_profile_routes(app):
|
||||||
@app.route("/api/profile")
|
@app.route("/api/profile")
|
||||||
def api_get_my_profile():
|
def api_get_my_profile():
|
||||||
|
|||||||
97
sanitizer.py
97
sanitizer.py
@ -1,97 +0,0 @@
|
|||||||
import html
|
|
||||||
from html.parser import HTMLParser
|
|
||||||
|
|
||||||
|
|
||||||
_ALLOWED_TAGS = {
|
|
||||||
"b",
|
|
||||||
"strong",
|
|
||||||
"i",
|
|
||||||
"em",
|
|
||||||
"u",
|
|
||||||
"p",
|
|
||||||
"br",
|
|
||||||
"ul",
|
|
||||||
"ol",
|
|
||||||
"li",
|
|
||||||
"blockquote",
|
|
||||||
"code",
|
|
||||||
"pre",
|
|
||||||
"a",
|
|
||||||
"h1",
|
|
||||||
"h2",
|
|
||||||
"h3",
|
|
||||||
"h4",
|
|
||||||
"h5",
|
|
||||||
"h6",
|
|
||||||
}
|
|
||||||
|
|
||||||
_ALLOWED_ATTRS = {
|
|
||||||
"a": {"href", "title", "target", "rel"},
|
|
||||||
}
|
|
||||||
|
|
||||||
_ALLOWED_SCHEMES = ("http://", "https://", "mailto:")
|
|
||||||
|
|
||||||
|
|
||||||
def _escape_attr(value: str) -> str:
|
|
||||||
return html.escape(value, quote=True)
|
|
||||||
|
|
||||||
|
|
||||||
def sanitize_html_fragment(raw: str) -> str:
|
|
||||||
class SafeHtmlParser(HTMLParser):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__(convert_charrefs=True)
|
|
||||||
self.parts = []
|
|
||||||
|
|
||||||
def handle_starttag(self, tag, attrs):
|
|
||||||
tag = tag.lower()
|
|
||||||
if tag not in _ALLOWED_TAGS:
|
|
||||||
return
|
|
||||||
|
|
||||||
allowed = _ALLOWED_ATTRS.get(tag, set())
|
|
||||||
clean_attrs = []
|
|
||||||
|
|
||||||
for key, value in attrs:
|
|
||||||
key = (key or "").lower()
|
|
||||||
value = value or ""
|
|
||||||
|
|
||||||
if key not in allowed:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if tag == "a" and key == "href":
|
|
||||||
lowered = value.strip().lower()
|
|
||||||
if not lowered.startswith(_ALLOWED_SCHEMES):
|
|
||||||
continue
|
|
||||||
|
|
||||||
clean_attrs.append((key, value))
|
|
||||||
|
|
||||||
if tag == "a":
|
|
||||||
attr_keys = {key for key, _ in clean_attrs}
|
|
||||||
if "target" not in attr_keys:
|
|
||||||
clean_attrs.append(("target", "_blank"))
|
|
||||||
if "rel" not in attr_keys:
|
|
||||||
clean_attrs.append(("rel", "noopener noreferrer"))
|
|
||||||
|
|
||||||
attr_text = "".join(
|
|
||||||
f' {key}="{_escape_attr(value)}"'
|
|
||||||
for key, value in clean_attrs
|
|
||||||
)
|
|
||||||
self.parts.append(f"<{tag}{attr_text}>")
|
|
||||||
|
|
||||||
def handle_endtag(self, tag):
|
|
||||||
tag = tag.lower()
|
|
||||||
if tag in _ALLOWED_TAGS and tag not in {"br"}:
|
|
||||||
self.parts.append(f"</{tag}>")
|
|
||||||
|
|
||||||
def handle_data(self, data):
|
|
||||||
self.parts.append(html.escape(data))
|
|
||||||
|
|
||||||
def handle_entityref(self, name):
|
|
||||||
self.parts.append(f"&{name};")
|
|
||||||
|
|
||||||
def handle_charref(self, name):
|
|
||||||
self.parts.append(f"&#{name};")
|
|
||||||
|
|
||||||
parser = SafeHtmlParser()
|
|
||||||
parser.feed(raw or "")
|
|
||||||
parser.close()
|
|
||||||
return "".join(parser.parts)
|
|
||||||
Loading…
x
Reference in New Issue
Block a user