From 1af4bc0d726fca629177ee18031352b02647b0d2 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Sat, 18 Apr 2026 23:36:57 +0200 Subject: [PATCH] see TODO.md --- .gitignore | 5 ++++ TODO.md | 18 ++++++++------- requirements.txt | Bin 74 -> 148 bytes src/log/Logger.py | 35 ++++++++++++++++++++++++++++ src/log/__init__.py | 0 src/main.py | 33 +++++++-------------------- src/profiles.py | 54 +++++++++++++++++++++++++++++++++++++++++--- src/sanitizer.py | 35 ++++++++++++++++++++++++++++ 8 files changed, 144 insertions(+), 36 deletions(-) create mode 100644 src/log/Logger.py create mode 100644 src/log/__init__.py create mode 100644 src/sanitizer.py diff --git a/.gitignore b/.gitignore index cb81377..e301605 100644 --- a/.gitignore +++ b/.gitignore @@ -668,3 +668,8 @@ fabric.properties # Android studio 3.1+ serialized cache file .idea/caches/build_file_checksums.ser + + +## ENVIRONMENT + +.env \ No newline at end of file diff --git a/TODO.md b/TODO.md index 8deb44f..752ecda 100644 --- a/TODO.md +++ b/TODO.md @@ -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: diff --git a/requirements.txt b/requirements.txt index 2fe7495d7a83dfa37393aaec32672c0d70f35159..70f79efe0322f90b03f510901b5bda97855e2362 100644 GIT binary patch delta 79 zcmea8!Z<-eHkl!xAqR+y7!nzBf!G!Z4H)zoOn}&IqO`t70YfE22}1@@NgjhPLkf^C RVMqnC%fLzvfy#`)7yxr$4mbb+ delta 8 PcmbQj=ruuMqLUT?3>*TD diff --git a/src/log/Logger.py b/src/log/Logger.py new file mode 100644 index 0000000..9b157da --- /dev/null +++ b/src/log/Logger.py @@ -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) diff --git a/src/log/__init__.py b/src/log/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main.py b/src/main.py index 25cd988..da5a536 100644 --- a/src/main.py +++ b/src/main.py @@ -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") diff --git a/src/profiles.py b/src/profiles.py index 1c15be7..4a0c6b5 100644 --- a/src/profiles.py +++ b/src/profiles.py @@ -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, } @@ -28,8 +76,8 @@ LOOSE = { ], "attributes": { "a": ["href", "title"], - "img": {"src", "alt", "style", "loading"} + "img": {"src","alt","style","loading"} }, "protocols": ["http", "https"], "strip": True, -} +} \ No newline at end of file diff --git a/src/sanitizer.py b/src/sanitizer.py new file mode 100644 index 0000000..f533354 --- /dev/null +++ b/src/sanitizer.py @@ -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"], + ) \ No newline at end of file