see TODO.md
This commit is contained in:
@@ -668,3 +668,8 @@ fabric.properties
|
|||||||
# Android studio 3.1+ serialized cache file
|
# Android studio 3.1+ serialized cache file
|
||||||
.idea/caches/build_file_checksums.ser
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## ENVIRONMENT
|
||||||
|
|
||||||
|
.env
|
||||||
@@ -11,17 +11,19 @@
|
|||||||
## To implement:
|
## To implement:
|
||||||
|
|
||||||
things not yet implemented in the program
|
things not yet implemented in the program
|
||||||
|
NOTE: That things which are in italic text are already implemented!
|
||||||
|
|
||||||
### url filtering:
|
### 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:
|
### Expland strict mode:
|
||||||
|
|||||||
Binary file not shown.
@@ -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)
|
||||||
+8
-25
@@ -1,30 +1,13 @@
|
|||||||
|
#
|
||||||
|
# src/profiles.py
|
||||||
|
# AGPL-3 - saningi-html
|
||||||
|
#
|
||||||
|
# entrypoint
|
||||||
|
#
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
import bleach
|
from sanitizer import sanitize
|
||||||
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"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def entry():
|
def entry():
|
||||||
parser = argparse.ArgumentParser(description="saningi-html sanitizer")
|
parser = argparse.ArgumentParser(description="saningi-html sanitizer")
|
||||||
|
|||||||
+51
-3
@@ -4,6 +4,49 @@
|
|||||||
#
|
#
|
||||||
# presets for sanitization of HTML
|
# 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 = {
|
STRICT = {
|
||||||
"tags" : [
|
"tags" : [
|
||||||
@@ -12,7 +55,12 @@ STRICT = {
|
|||||||
"br", "hr",
|
"br", "hr",
|
||||||
"h1", "h2", "h3", "h4", "h5", "h6"
|
"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": [],
|
"protocols": [],
|
||||||
"strip": True,
|
"strip": True,
|
||||||
}
|
}
|
||||||
@@ -28,8 +76,8 @@ LOOSE = {
|
|||||||
],
|
],
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"a": ["href", "title"],
|
"a": ["href", "title"],
|
||||||
"img": {"src", "alt", "style", "loading"}
|
"img": {"src","alt","style","loading"}
|
||||||
},
|
},
|
||||||
"protocols": ["http", "https"],
|
"protocols": ["http", "https"],
|
||||||
"strip": True,
|
"strip": True,
|
||||||
}
|
}
|
||||||
@@ -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"],
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user