see TODO.md

This commit is contained in:
2026-04-18 23:36:57 +02:00
parent bbefb1749d
commit 1af4bc0d72
8 changed files with 144 additions and 36 deletions
+5
View File
@@ -668,3 +668,8 @@ fabric.properties
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
## ENVIRONMENT
.env
+10 -8
View File
@@ -11,17 +11,19 @@
## To implement:
things not yet implemented in the program
NOTE: That things which are in italic text are already implemented!
### url filtering:
- [ ] block external URL in strict mode (eg a link with href to some porn site or something)
- [ ] allow relative paths:
- eg "/path/to/my/awesome/post.html"
- [ ] apply to any of these:
- a
- img
- link href
- [ ] add custom attribute filter function
- *[x] block external URL in strict mode (eg a link with href to some porn site or something)*
- *[x] allow relative paths:*
*- eg "/path/to/my/awesome/post.html"*
- *[x] apply to any of these:*
- *a*
- *img*
- *link href*
- *[x] add custom attribute filter function*
- [] load dotenv (chore for profiles.py) ; do this someday when implementing JSON configs. Or dotenv stuff : PRIO:HIGH
---
### Expland strict mode:
BIN
View File
Binary file not shown.
+35
View File
@@ -0,0 +1,35 @@
#
# src/log/Logger.py
# AGPL-3.0 - saningi-html
# usage:
# insantiate using logger = Logger()
# import using import .log.Logger
#
#
import time
import sys
import colorama
colorama.init(autoreset=True)
class Logger:
@staticmethod
def _now() -> str:
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
@staticmethod
def log_error(message: str) -> None:
print(f"{colorama.Fore.RED}[ ERROR@{Logger._now()} ]: {message}", file=sys.stderr)
@staticmethod
def log_info(message: str) -> None:
print(f"{colorama.Fore.CYAN}[ INFO@{Logger._now()} ]: {message}", file=sys.stderr)
@staticmethod
def log_debug(message: str) -> None:
print(f"[ DEBUG@{Logger._now()} ]: {message}", file=sys.stderr)
@staticmethod
def log_warning(message: str) -> None:
print(f"{colorama.Fore.YELLOW}[ WARNING@{Logger._now()} ]: {message}", file=sys.stderr)
View File
+8 -25
View File
@@ -1,30 +1,13 @@
#
# src/profiles.py
# AGPL-3 - saningi-html
#
# entrypoint
#
import sys
import argparse
import bleach
from profiles import STRICT, LOOSE
PROFILES = {
"strict": STRICT,
"loose": LOOSE,
}
def sanitize(html : str, mode : str) -> str:
# this should be used with caution! it is for trusted
# individuals only
if mode == "passthrough":
return html
config = PROFILES.get(mode)
if not config:
raise ValueError(f"Unknown mode! : {mode}")
return bleach.clean(
html,
tags=config["tags"],
attributes=config["attributes"],
protocols=config["protocols"],
strip=config["strip"],
)
from sanitizer import sanitize
def entry():
parser = argparse.ArgumentParser(description="saningi-html sanitizer")
+49 -1
View File
@@ -4,6 +4,49 @@
#
# presets for sanitization of HTML
#
import re
from dotenv import dotenv_values
from urllib.parse import urlparse
def is_safe_url(url : str, allowed_domain=None, allow_relative : bool = True) -> bool:
"""
Determine wether or not a URL is safe or not.
Safe URL's are only local so /this/url
Unsafe URL's are external ones like https://pornhub.com
"""
if not url:
return False
if allow_relative:
if url.startswith(("#", "/", "./", "../")):
return True
if url.startswith("//"):
if not allowed_domain:
return False
parsed = urlparse("http:" + url)
domain = parsed.netloc or parsed.path.split("/")[0]
return domain in allowed_domain
parsed = urlparse(url)
if parsed.scheme in ("http", "https"):
if not allowed_domain:
return False
return parsed.netloc in allowed_domain
return False
def create_url_filter(allowed_attrs, allowed_domains=None, allow_relative=True):
def filter_func(tag, name, value):
if name not in allowed_attrs:
return None
if name in ('href', 'src'):
if is_safe_url(value, allowed_domains, allow_relative):
return value
return None
return value
return filter_func
STRICT = {
"tags" : [
@@ -12,7 +55,12 @@ STRICT = {
"br", "hr",
"h1", "h2", "h3", "h4", "h5", "h6"
],
"attributes": {},
"attributes": {
# TODO: add loading dotenv variables
"a": create_url_filter(["href"], allowed_domains=[], allow_relative=True),
"img" : create_url_filter(["src", "alt"], allowed_domains=[], allow_relative=True),
"link" : create_url_filter(["href"], allowed_domains=[], allow_relative=True),
},
"protocols": [],
"strip": True,
}
+35
View File
@@ -0,0 +1,35 @@
import bleach
from profiles import STRICT, LOOSE
from log.Logger import *
logger = Logger()
PROFILES = {
"strict": STRICT,
"loose": LOOSE,
}
def sanitize(html : str, mode : str) -> str:
# this should be used with caution! it is for trusted
# individuals only
if mode == "passthrough":
return html
config = PROFILES.get(mode)
if not config:
raise ValueError(f"Unknown mode! : {mode}")
logger.log_debug(
"called sanitizer! using this as args: \n" +
str(html) +
str(config["tags"]) + "\n" +
str(config["protocols"]) + "\n" +
str(config["strip"])
)
return bleach.clean(
html,
tags=config["tags"],
attributes=config["attributes"],
protocols=config["protocols"],
strip=config["strip"],
)