"""Godkendelsesstemplet på blogindlæg: HMAC-SHA256 over sti, indhold og billeder. canonical() skal matche godkendelsestjenesten (service/app.py i agw/pw-drafts) byte for byte. """ import hashlib import hmac import os import re BLOG = "content/blog" # Præcis den linje tjenesten skriver. Alt andet i filen er signeret, også linjer der # ligner stemplet. Zola og dette script kan være uenige om hvor frontmatter slutter, så # en løsere regel ville give plads til usigneret tekst. STAMP_LINE = re.compile(r' godkendt: "(v1:[0-9a-f]{64})"') def canonical(rel, text, assets=()): """assets: [(sti, sha256-hex)] for billederne i bundlen, sorteret efter sti.""" lines = text.replace("\r\n", "\n").split("\n") if lines and lines[0] == "---" and "---" in lines[1:]: end = lines.index("---", 1) hit = next((i for i in range(1, end) if STAMP_LINE.fullmatch(lines[i])), None) if hit: del lines[hit] return f"{rel}\n" + "\n".join(lines) + "".join(f"\n@asset {p} {h}" for p, h in assets) def sign(key, rel, text, assets=()): return "v1:" + hmac.new(key, canonical(rel, text, assets).encode(), hashlib.sha256).hexdigest() def sha256_file(path): with open(path, "rb") as f: return hashlib.sha256(f.read()).hexdigest() def assets_for(slug): """Alt i bundlen ud over index.md er billeder, og de ligger fladt.""" d = f"{BLOG}/{slug}" out = [] for name in os.listdir(d): if name == "index.md": continue p = f"{d}/{name}" if not os.path.isfile(p): raise ValueError(f"{p}: undermapper understøttes ikke") if name.endswith(".md"): raise ValueError(f"{p}: kun index.md må være markdown i en bundle") out.append((p, sha256_file(p))) return sorted(out) def read_text(path): with open(path, "rb") as f: return f.read().decode() def stamp_of(text): """(stempel eller None, om indlægget er markeret draft: true)""" lines = text.replace("\r\n", "\n").split("\n") end = lines.index("---", 1) if lines[0] == "---" and "---" in lines[1:] else -1 if end < 0: return None, False fm = lines[1:end] g = next((m for m in map(STAMP_LINE.fullmatch, fm) if m), None) draft = any(re.fullmatch(r"draft:\s*true\s*", l) for l in fm) return (g[1] if g else None), draft