🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
#!/usr/bin/env python3
"""Punkt 7: http:// -> https:// w polach source/sources/url receptur.
- Podmienia TYLKO wystapienia zweryfikowane jako SAFE_HTTPS / SAFE_HTTPS_UNVERIFIED.
- Granica dopasowania: po URL musi byc cudzyslow/bialy znak/przecinek/nawias/koniec,
zeby nie trafic w prefiks dluzszego URL-a (np. url: 'http://x/' + source http://x/files/...).
- Jesli ten sam URL wystepuje tez w build/hooks, plik obslugiwany jest zakresowo
(tylko blok klucza), a nie globalnie.
- Zachowuje CRLF (odczyt/zapis z newline='').
- Waliduje YAML przed i po; sprawdza, ze zmienily sie dokladnie oczekiwane wartosci.
"""
import collections
import json
import os
import re
import sys
import yaml
R = "/var/lib/pagan-sync/recipes"
AUDIT = "/tmp/https-audit.json"
CONV = ("SAFE_HTTPS", "SAFE_HTTPS_UNVERIFIED")
LIST_KEYS = ("source", "sources")
SCALAR_KEYS = ("url",)
APPLY = "--apply" in sys.argv
def as_list(v):
if v is None:
return []
return v if isinstance(v, list) else [v]
def url_pat(url):
return re.compile(re.escape(url) + r'(?=["\'\s\],]|$)')
def block_span(text, key, top_only=True):
"""Zwraca (start, end) zakresu bloku listy dla klucza (razem z linia klucza)."""
m = re.search(r"(?m)^%s:" % re.escape(key), text)
if not m:
return None
line_end = text.find("\n", m.start())
if line_end == -1:
line_end = len(text)
rest = text[m.end():line_end].replace("\r", "").strip()
if rest and not rest.startswith("["):
return (m.start(), line_end) # skalarny/nie-listowy
pos = line_end + 1
end = line_end
item_indent = None
while pos < len(text):
nl = text.find("\n", pos)
if nl == -1:
nl = len(text)
raw = text[pos:nl].replace("\r", "")
if raw.strip() == "":
break
mi = re.match(r"^(\s*)-\s", raw)
if mi:
if item_indent is None or len(mi.group(1)) == item_indent:
item_indent = len(mi.group(1))
end = nl
pos = nl + 1
continue
if raw[0] in " \t":
end = nl
pos = nl + 1
continue
break
return (m.start(), end)
def in_other_value(d, urls):
"""Czy ktorys URL wystepuje w build/hooks (poza polami zrodel)?"""
hits = []
for k in ("build", "hooks", "pre-install", "post-install", "prepare"):
v = d.get(k)
if v is None:
continue
s = json.dumps(v) if not isinstance(v, str) else v
for u in urls:
if u in s:
hits.append((k, u))
return hits
def main():
audit = json.load(open(AUDIT))
occ = [i for c in CONV for i in audit.get(c, [])]
by_file = collections.defaultdict(list)
for i in occ:
by_file[i["recipe"]].append(i)
changed_files = 0
changed_occ = 0
problems = []
scoped_files = []
report = []
for rel in sorted(by_file):
path = os.path.join(R, rel)
text = open(path, newline="", encoding="utf-8").read()
try:
before = yaml.safe_load(text) or {}
except Exception as e:
problems.append(f"{rel}: YAML przed nie parsuje sie: {e}")
continue
targets = by_file[rel]
urls = [t["raw"] for t in targets]
hits = in_other_value(before, urls)
new = text
applied = 0
if hits:
scoped_files.append((rel, hits))
# tryb zakresowy: podmieniaj tylko w blokach kluczy
for key in LIST_KEYS + SCALAR_KEYS:
key_urls = [t["raw"] for t in targets if t["key"] == key]
if not key_urls:
continue
sp = block_span(new, key)
if not sp:
problems.append(f"{rel}: zakresowy, brak bloku {key}")
continue
s, e = sp
seg = new[s:e]
for u in key_urls:
seg2, n = url_pat(u).subn("https://" + u[7:], seg, count=1)
if n:
applied += n
else:
problems.append(f"{rel}: zakresowo nie znaleziono {u}")
seg = seg2
new = new[:s] + seg + new[e:]
else:
for t in targets:
new2, n = url_pat(t["raw"]).subn("https://" + t["raw"][7:], new, count=1)
if n:
applied += n
else:
problems.append(f"{rel}: nie znaleziono doslownie {t['raw']}")
new = new2
if applied == 0:
continue
try:
after = yaml.safe_load(new) or {}
except Exception as e:
problems.append(f"{rel}: YAML PO edycji nie parsuje sie: {e}")
continue
# walidacja: zmienily sie dokladnie oczekiwane wartosci
ok = True
for key in LIST_KEYS + SCALAR_KEYS:
ob, oa = before.get(key), after.get(key)
if ob == oa:
continue
wb, wa = as_list(ob), as_list(oa)
if len(wb) != len(wa):
problems.append(f"{rel}: {key} zmieniona dlugosc listy")
ok = False
continue
for i, (x, y) in enumerate(zip(wb, wa)):
if x == y:
continue
expect = str(x).replace("http://", "https://", 1)
if str(y) != expect:
problems.append(f"{rel}: {key}[{i}] nieoczekiwana zmiana: {x!r} -> {y!r}")
ok = False
# zadne inne klucze nie moga sie zmienic
for k in set(before) | set(after):
if k in LIST_KEYS + SCALAR_KEYS:
continue
if before.get(k) != after.get(k):
problems.append(f"{rel}: nieoczekiwana zmiana klucza {k}")
ok = False
if not ok:
continue
if APPLY:
with open(path, "w", newline="", encoding="utf-8") as f:
f.write(new)
changed_files += 1
changed_occ += applied
report.append(f"{'ZMIENIONO' if APPLY else 'DRY'}: {rel} ({applied})")
for line in report:
print(line)
print()
print(f"plikow: {changed_files} | wystapien: {changed_occ} | tryb: {'APPLY' if APPLY else 'DRY-RUN'}")
if scoped_files:
print(f"\npliki z trybem zakresowym (URL tez w build/hooks): {len(scoped_files)}")
for rel, hits in scoped_files:
print(f" {rel}: {hits}")
if problems:
print(f"\nPROBLEMY ({len(problems)}):")
for p in problems:
print(" !", p)
if __name__ == "__main__":
sys.exit(main())