🔒 Repository is read-only – file editing is disabled.

PaganLinux/pagan-web-v2/blueprints/submit.py main

112 linii Raw ← Powrót
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
import os, json, re, uuid
from datetime import datetime
from pathlib import Path
from flask import Blueprint, render_template, request, redirect, flash

submit_bp = Blueprint("submit", __name__)

PENDING_DIR = Path("/var/lib/pagan-web/pending")
# Tylko kategorie obecne w drzewie recipes/ (core, gui, utils) – reszta
# została skonsolidowana podczas migracji NuTyX -> PAGBUILD.yaml.
ALLOWED_CATS = {"core", "gui", "utils"}
NAME_RE = re.compile(r"^[a-z0-9][a-z0-9+_.-]*$")
VER_RE = re.compile(r"^[0-9][a-zA-Z0-9._+-]*$")
URL_RE = re.compile(r"^https?://\S+$")
ITEM_RE = re.compile(r"^[a-zA-Z0-9+_.-]+$")


def _validate(data):
    """Walidacja formularza – twarde reguły, brak możliwości wstrzyknięcia."""
    errors = []
    pkgname = (data.get("pkgname") or "").strip()
    if not NAME_RE.match(pkgname):
        errors.append("Nazwa pakietu: tylko [a-z0-9+_.-], musi zaczynać się literą/cyfrą")
    pkgver = (data.get("pkgver") or "").strip()
    if not VER_RE.match(pkgver):
        errors.append("Wersja: tylko [0-9a-zA-Z._+-], musi zaczynać się cyfrą")
    pkgrel = (data.get("pkgrel") or "1").strip()
    if not re.match(r"^\d+$", pkgrel):
        errors.append("pkgrel: tylko liczba całkowita")
    for f, label in (("pkgdesc", "Opis"), ("url", "URL projektu")):
        v = (data.get(f) or "").strip()
        if not v:
            errors.append(f"{label}: wymagany")
        elif len(v) > 300:
            errors.append(f"{label}: maksymalnie 300 znaków")
    if data.get("url") and not URL_RE.match((data.get("url") or "").strip()):
        errors.append("URL projektu: musi zaczynać się od http:// lub https://")
    srcs = [s.strip() for s in (data.get("source") or "").splitlines() if s.strip()]
    if not srcs:
        errors.append("Źródło (source): wymagany przynajmniej jeden URL")
    for s in srcs:
        if not URL_RE.match(s):
            errors.append(f"Źródło: nieprawidłowy URL: {s[:60]}")
        if len(s) > 500:
            errors.append(f"Źródło: URL za długi: {s[:60]}...")
    for field, label in (("build", "build"), ("package", "package")):
        v = data.get(field) or ""
        if not v.strip():
            errors.append(f"Faza {label}: wymagana")
        elif len(v) > 8000:
            errors.append(f"Faza {label}: maksymalnie 8000 znaków")
    cat = (data.get("category") or "").strip()
    if cat not in ALLOWED_CATS:
        errors.append("Nieprawidłowa kategoria")
    for field, label in (("depends", "depends"), ("makedepends", "makedepends"), ("license", "license")):
        for it in (data.get(field) or "").split(","):
            it = it.strip()
            if it and not ITEM_RE.match(it):
                errors.append(f"{label}: nieprawidłowa wartość: {it[:40]}")
    return errors


@submit_bp.route("/submit-recipe", methods=["GET", "POST"])
def submit_recipe():
    if request.method == "POST":
        data = {
            "pkgname": (request.form.get("pkgname") or "").strip(),
            "pkgver": (request.form.get("pkgver") or "").strip(),
            "pkgrel": (request.form.get("pkgrel") or "1").strip(),
            "pkgdesc": (request.form.get("pkgdesc") or "").strip(),
            "url": (request.form.get("url") or "").strip(),
            "category": (request.form.get("category") or "libs").strip(),
            "license": (request.form.get("license") or "").strip(),
            "depends": (request.form.get("depends") or "").strip(),
            "makedepends": (request.form.get("makedepends") or "").strip(),
            "source": (request.form.get("source") or "").strip(),
            "sha256sums": (request.form.get("sha256sums") or "").strip(),
            "build": (request.form.get("build") or "").strip(),
            "package": (request.form.get("package") or "").strip(),
        }
        errors = _validate(data)
        if errors:
            for e in errors:
                flash(f"❌ {e}", "error")
            return render_template("submit_recipe.html", data=data, cats=sorted(ALLOWED_CATS))

        if request.form.get("terms") != "on":
            flash("❌ Wymagana akceptacja regulaminu zgłaszania receptur.", "error")
            return render_template("submit_recipe.html", data=data, cats=sorted(ALLOWED_CATS))

        rid = uuid.uuid4().hex[:12]
        PENDING_DIR.mkdir(parents=True, exist_ok=True)
        rec = {
            "id": rid,
            "status": "pending",
            "created": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "terms_accepted": True,
            "data": data,
        }
        (PENDING_DIR / f"{rid}.json").write_text(json.dumps(rec, indent=2, ensure_ascii=False))
        # Audit log
        try:
            import time as _t
            ip = request.headers.get("X-Real-IP", request.remote_addr) or "?"
            user = session.get("username", "?")
            with open("/var/lib/pagan-web/audit.log", "a") as af:
                af.write(f"{_t.strftime('%Y-%m-%d %H:%M:%S')} [{user}] {ip}  SUBMIT_RECIPE {data['pkgname']}-{data['pkgver']}\n")
        except Exception:
            pass
        flash(f"✅ Zgłoszenie #{rid} przyjęte! Czeka na zatwierdzenie przez administratora.", "ok")
        return redirect("/submit-recipe")
    return render_template("submit_recipe.html", data={}, cats=sorted(ALLOWED_CATS))