#!/usr/bin/env python3 """Audyt http:// w zrodlach receptur - wersja przyrostowa. Faza A: skan receptur -> mapa host -> lista (rel, key, url). Zapisywana od razu. Faza B: test https:// dla kazdego hosta (max 2 URL-e), wynik logowany na biezaco i dopisywany do cache, zeby przerwanie nie tracilo pracy. """ import collections import json import os import re import ssl import sys import urllib.error import urllib.request import yaml R = "/var/lib/pagan-sync/recipes" KEYS = ("source", "sources", "url") SCAN = "/tmp/http-audit-scan.json" CACHE = "/tmp/http-audit-cache.json" UA = {"User-Agent": "pagan-audit/1.0"} def as_list(v): if v is None: return [] return v if isinstance(v, list) else [v] def log(msg): print(msg, flush=True) def phase_scan(): findings = collections.defaultdict(list) for dp, _d, fs in os.walk(R): if "PAGBUILD.yaml" not in fs: continue p = os.path.join(dp, "PAGBUILD.yaml") rel = p[len(R) + 1:] try: d = yaml.safe_load(open(p, encoding="utf-8")) or {} except Exception: continue for k in KEYS: for v in as_list(d.get(k)): s = str(v) m = re.match(r"^https?://([^/\s]+)", s) if s.startswith("http://") and m: findings[m.group(1)].append([rel, k, s]) json.dump(findings, open(SCAN, "w"), indent=1, ensure_ascii=False) log(f"Faza A: hostow={len(findings)} wystapien={sum(len(v) for v in findings.values())}") log(f"zapisano {SCAN}") return findings def try_url(url, timeout=12): ctx = ssl.create_default_context() try: req = urllib.request.Request(url, headers=UA, method="HEAD") with urllib.request.urlopen(req, timeout=timeout, context=ctx) as r: return True, r.status except urllib.error.HTTPError as e: if e.code in (403, 405, 400, 501): pass # sprobuj GET else: return False, f"HTTP {e.code}" except Exception: pass try: req = urllib.request.Request(url, headers=UA, method="GET") with urllib.request.urlopen(req, timeout=timeout, context=ctx) as r: return True, r.status except urllib.error.HTTPError as e: if e.code in (403, 405): return True, e.code return False, f"HTTP {e.code}" except Exception as e: return False, type(e).__name__ def phase_test(findings): cache = {} if os.path.exists(CACHE): try: cache = json.load(open(CACHE)) except Exception: cache = {} def save(): json.dump(cache, open(CACHE, "w"), indent=1, ensure_ascii=False) results = {} order = sorted(findings, key=lambda h: -len(findings[h])) for host in order: urls = findings[host] ok_any = False statuses = [] for rel, k, url in urls[:2]: hu = "https://" + url[len("http://"):] if hu in cache: ok, st = cache[hu] else: ok, st = try_url(hu) cache[hu] = [ok, st] save() statuses.append(f"{st}") if ok: ok_any = True break results[host] = {"n": len(urls), "ok": ok_any, "statuses": statuses, "sample": urls[0][2]} log(f"{'OK ' if ok_any else 'FAIL'} n={len(urls):4d} {host:45s} {','.join(statuses)}") json.dump(results, open("/tmp/http-audit-results.json", "w"), indent=1, ensure_ascii=False) return results def main(): findings = phase_scan() results = phase_test(findings) n_ok = sum(1 for r in results.values() if r["ok"]) ent_ok = sum(r["n"] for r in results.values() if r["ok"]) total = sum(r["n"] for r in results.values()) log("") log(f"hosty https dziala: {n_ok}/{len(results)}") log(f"wystapien do zmiany: {ent_ok}/{total}") if __name__ == "__main__": sys.exit(main())