mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
82 lines
3.0 KiB
Python
82 lines
3.0 KiB
Python
"""HTML or Markdown to PDF via WeasyPrint (no-phone-home posture).
|
|
Remote references (src/href/action/url()) are REJECTED before conversion
|
|
with a clear error listing up to 5 offending URLs. The url_fetcher remains
|
|
as a defense-in-depth backstop: it blocks any reference that slips past
|
|
the scan, raising ValueError so WeasyPrint omits the resource with zero
|
|
outbound requests.
|
|
Args: {"path": in, "out": o, "mode": "html"|"markdown"}. Prints {"ok": true}."""
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
_REMOTE_REF_RE = re.compile(
|
|
r'(?:src|href|action)\s*=\s*["\']?\s*(https?://[^\s"\'>\)]{1,200})',
|
|
re.IGNORECASE,
|
|
)
|
|
_REMOTE_CSS_URL_RE = re.compile(
|
|
r'url\s*\(\s*["\']?\s*(https?://[^\s"\'>\)]{1,200})',
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
|
|
def _find_remote_refs(source):
|
|
"""Return up to 5 remote URLs found in HTML/CSS source."""
|
|
refs = []
|
|
for pattern in (_REMOTE_REF_RE, _REMOTE_CSS_URL_RE):
|
|
for m in pattern.finditer(source):
|
|
refs.append(m.group(1)[:120])
|
|
if len(refs) >= 5:
|
|
return refs
|
|
return refs
|
|
|
|
|
|
def main():
|
|
args = json.loads(sys.argv[1]) if len(sys.argv) > 1 else {}
|
|
path, out, mode = args.get("path"), args.get("out"), args.get("mode", "html")
|
|
if not path or not out or mode not in ("html", "markdown"):
|
|
print(json.dumps({"error": "missing path/out or bad mode"}))
|
|
sys.exit(1)
|
|
try:
|
|
from weasyprint import HTML
|
|
from weasyprint.urls import default_url_fetcher
|
|
except ImportError:
|
|
print(json.dumps({"error": "weasyprint not installed"}))
|
|
sys.exit(1)
|
|
|
|
def no_remote_fetcher(url, *fargs, **kwargs):
|
|
if url.startswith("data:"):
|
|
return default_url_fetcher(url, *fargs, **kwargs)
|
|
raise ValueError(f"remote resources are disabled: {url[:120]}")
|
|
|
|
try:
|
|
with open(path, "r", encoding="utf-8", errors="replace") as fh:
|
|
source = fh.read()
|
|
if mode == "markdown":
|
|
try:
|
|
import markdown as md
|
|
except ImportError:
|
|
print(json.dumps({"error": "markdown not installed"}))
|
|
sys.exit(1)
|
|
body = md.markdown(source, extensions=["tables", "fenced_code"])
|
|
source = (
|
|
"<!doctype html><html><head><meta charset=\"utf-8\">"
|
|
"<style>body{font-family:sans-serif;max-width:46em;margin:2em auto;}"
|
|
"code,pre{background:#f4f4f4;}table,td,th{border:1px solid #999;border-collapse:collapse;padding:4px;}</style>"
|
|
f"</head><body>{body}</body></html>"
|
|
)
|
|
remote_refs = _find_remote_refs(source)
|
|
if remote_refs:
|
|
print(json.dumps({"error": f"remote resources are disabled: {', '.join(remote_refs)}"}))
|
|
sys.exit(1)
|
|
HTML(string=source, url_fetcher=no_remote_fetcher, base_url=None).write_pdf(out)
|
|
print(json.dumps({"ok": True}))
|
|
except SystemExit:
|
|
raise
|
|
except Exception as exc: # noqa: BLE001
|
|
print(json.dumps({"error": str(exc)}))
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|