"""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"
  • {html.escape(n)}
  • " for n in finding.get("notes") or [] ) anchor = finding.get("reopen_anchor") or {} anchor_html = "" if anchor: anchor_html = ( "

    " "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 '—')})" "

    " ) return ( f"
    " f"

    " "COOKIE-CONSENT-UX-001 — Mobile Consent-Reachability

    " f"

    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"" f"{anchor_html}" "
    " )