45 lines
3.1 KiB
Python
45 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Unit checks for theme validation and the customCss sanitiser."""
|
|
import os, sys
|
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
|
from market.theme import sanitize_css, sanitize_patch, build_theme_action, PRESETS
|
|
|
|
fails = 0
|
|
def check(name, cond):
|
|
global fails
|
|
fails += not cond
|
|
print(("PASS " if cond else "FAIL ") + name)
|
|
|
|
evil = ("@import url(https://evil.example/x.css); .card{color:red!important;position:fixed;z-index:99;--p-ink:transparent;"
|
|
"background:url(https://evil.example/t.png)} body{display:none} :root{--acc:red} h1{background:url(https://images.unsplash.com/a.jpg);margin:-500px}"
|
|
" <script>alert(1)</script> .x{color:expression(alert(1))} .y{behavior:url(x.htc)} .z::before{content:'x'}"
|
|
" .t{transform:scale(0)} .k{color:blue}")
|
|
out = sanitize_css(evil)
|
|
check("css: @import stripped", "@" not in out and "evil.example/x.css" not in out)
|
|
check("css: !important stripped", "important" not in out)
|
|
check("css: position/z-index/content/transform dropped", not any(k in out for k in ("position", "z-index", "content", "transform")))
|
|
check("css: custom properties dropped", "--" not in out)
|
|
check("css: non-allowlisted url() neutralised", "evil.example" not in out and "url(https://images.unsplash.com/a.jpg)" in out)
|
|
check("css: body/:root selectors dropped", "body" not in out and ":root" not in out)
|
|
check("css: script/expression/behavior gone", not any(k in out.lower() for k in ("<script", "expression", "behavior", "alert(1)")))
|
|
check("css: negative margins dropped", "-500px" not in out)
|
|
check("css: harmless rule kept", ".k{color:blue}" in out)
|
|
|
|
p = sanitize_patch({"mode": "light", "palette": {"accent": "green", "text": "red;}body{x"}, "font": "comic sans", "fontScale": 9,
|
|
"radius": -5, "columns": 12, "layout": "list", "chat": {"position": "left", "width": 5000},
|
|
"copy": {"headline": "<b>Hi</b> there", "chips": ["Bikes", {"label": "Garden", "say": "garden stuff"}]},
|
|
"sections": {"hidden": ["bullets", "nope"], "order": ["results", "hero"]}, "evil": "x",
|
|
"background": {"kind": "image", "url": "https://evil.example/bg.jpg"}})
|
|
check("patch: bad colour dropped, good kept", p["palette"] == {"accent": "green"})
|
|
check("patch: font alias resolved", p["font"] == "Comic Neue")
|
|
check("patch: numbers clamped", p["fontScale"] == 1.75 and p["radius"] == 0 and p["columns"] == 6 and p["chat"]["width"] == 600)
|
|
check("patch: html stripped from copy", "<" not in p["copy"]["headline"])
|
|
check("patch: sections validated", p["sections"]["hidden"] == ["bullets"] and p["sections"]["order"][:2] == ["results", "hero"])
|
|
check("patch: unknown keys and non-allowlisted image dropped", "evil" not in p and "background" not in p)
|
|
a = build_theme_action({"preset": "Retro"})
|
|
check("preset: retro expands", a["preset"] == "retro" and a["base"]["font"] == "VT323")
|
|
check("preset: alias", build_theme_action({"preset": "cyberpunk"})["preset"] == "neon")
|
|
check("reset action", build_theme_action({"reset": True})["reset"] is True)
|
|
check("all presets validate cleanly", all(sanitize_patch(v) for v in PRESETS.values()))
|
|
sys.exit(1 if fails else 0)
|