"""B1 wiring — Mobile Consent-Reachability check + HTML block. Fetches the homepage of the first submitted URL, runs the static `evaluate_reachability` analysis on the footer, and renders the result as an HTML block for the audit mail. Only renders a block when the check FAILS — a passing site doesn't need a block. The block is severity-colored and lists the specific notes that triggered the finding (missing reopen anchor, new-tab break, browser-deflection language). """ from __future__ import annotations import html import logging import httpx from compliance.services.consent_reachability_check import ( evaluate_reachability, ) from ._helpers import _update logger = logging.getLogger(__name__) async def run_b1(state: dict) -> None: """Run the reachability check + render HTML. Mutates state in place.""" req = state["req"] check_id = state["check_id"] homepage_url = "" for d in req.documents: if d.url: from urllib.parse import urlparse p = urlparse(d.url) if p.scheme and p.netloc: homepage_url = f"{p.scheme}://{p.netloc}/" break if not homepage_url: return _update(check_id, "Mobile Consent-Reachability prüfen...", 95) try: async with httpx.AsyncClient( timeout=20.0, follow_redirects=True, headers={"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 " "like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) " "Version/17.5 Mobile/15E148 Safari/604.1"}, ) as c: r = await c.get(homepage_url) if r.status_code != 200: logger.info("B1: homepage fetch %s → HTTP %d", homepage_url, r.status_code) return page_html = r.text except Exception as e: logger.warning("B1: homepage fetch failed: %s", e) return finding = evaluate_reachability(page_html, homepage_url) state["reachability_finding"] = finding state["reachability_html"] = _render_block(finding) logger.info( "B1 Reachability: passed=%s severity=%s reason=%s", finding["passed"], finding.get("severity"), finding.get("severity_reason"), ) def _render_block(finding: dict) -> str: """Render the reachability finding as an audit-mail HTML block.""" if finding["passed"]: return "" sev = (finding.get("severity") or "").upper() color = "#dc2626" if sev == "HIGH" else "#f59e0b" notes_html = "".join( f"
"
"Gefundener Footer-Link: "
f"{html.escape((anchor.get('text') or '')[:80])} "
f"→ {html.escape((anchor.get('href') or '')[:120])} "
f"(target_class: {html.escape(anchor.get('target_class') or '—')})"
"
Severity: " f"{sev} ({html.escape(finding.get('severity_reason') or '')})
" "" "Art. 7 Abs. 3 DSGVO: Widerruf muss so einfach wie Erteilung sein. " "Auf Mobile-Safari konnten wir folgendes Problem feststellen:
" f"