from __future__ import annotations import argparse import asyncio import base64 import hashlib import json import re from collections import deque from dataclasses import dataclass from pathlib import Path from urllib.parse import urljoin, urldefrag, urlparse from playwright.async_api import async_playwright from .ensure_playwright import ensure_chromium_installed RESOURCE_TO_DIRECTIVE = { "script": "script-src", "stylesheet": "style-src", "image": "img-src", "font": "font-src", "media": "media-src", "xhr": "connect-src", "fetch": "connect-src", "websocket": "connect-src", "eventsource": "connect-src", } BASELINE_DIRECTIVES = { "default-src": {"'self'"}, "base-uri": {"'self'"}, "object-src": {"'none'"}, "frame-ancestors": {"'self'"}, "form-action": {"'self'"}, } def origin_of(url: str) -> str: p = urlparse(url) if not p.scheme or not p.netloc: return "" return f"{p.scheme}://{p.netloc}" def sha256_base64(s: str) -> str: h = hashlib.sha256(s.encode("utf-8")).digest() return base64.b64encode(h).decode("ascii") def normalize_csp_string(csp: str) -> str: s = (csp or "").strip() if not s: return s return s if s.endswith(";") else s + ";" async def collect_inline(page, *, max_attr_hashes: int = 2000): """ Collect inline