"""serve.py: валидация int-параметров, eviction JOBS, токен на POST, лимит тела.""" from pathlib import Path p = Path(r"C:/Users/User/tmp/omni-parse/omni/serve.py") s = p.read_text(encoding="utf-8") rep = [] if "\nimport os\n" not in s: s = s.replace("import json\n", "import json\nimport os\n", 1) rep.append("+ import os") helper = ''' UI_TOKEN = os.environ.get("OMNI_UI_TOKEN", "") def _ui_auth(req): """Панель наружу: без токена мутации открыты любому, кто дошёл до порта.""" if not UI_TOKEN: return True return req.headers.get("X-Omni-Token") == UI_TOKEN def _intv(q, key, default, lo, hi): """int из query без падений: ?limit=abc раньше давал трейс и обрыв соединения, ?limit=-1 отдавал всю базу (SQLite LIMIT -1 = без лимита).""" try: v = int((q.get(key) or [str(default)])[0]) except (TypeError, ValueError): return default return max(lo, min(hi, v)) ''' assert "def _job(jid, **kw):" in s s = s.replace("def _job(jid, **kw):", helper.strip("\n") + "\n\n\ndef _job(jid, **kw):", 1) s = s.replace(''' with _LOCK: cur = JOBS.get(jid) or {} cur.update(kw) JOBS[jid] = cur''', ''' with _LOCK: cur = JOBS.get(jid) or {} cur.update(kw) cur["_ts"] = time.time() JOBS[jid] = cur if len(JOBS) > 400: # раньше росло без границ (500 запусков -> 500 записей) dead = sorted(JOBS.items(), key=lambda kv: kv[1].get("_ts", 0))[:len(JOBS) - 300] for k, _v in dead: JOBS.pop(k, None)''') rep.append("JOBS eviction") pairs = [ ('limit = max(1, min(500, int((q.get("limit") or ["80"])[0])))', 'limit = _intv(q, "limit", 80, 1, 500)'), ('lim = max(1, min(200, int((q.get("limit") or ["40"])[0])))', 'lim = _intv(q, "limit", 40, 1, 200)'), ('lim = max(1, min(40, int((q.get("limit") or ["14"])[0])))', 'lim = _intv(q, "limit", 14, 1, 40)'), ('limit = min(int((q.get("limit") or ["40"])[0]), 200)', 'limit = _intv(q, "limit", 40, 1, 200)'), ('offset = int((q.get("offset") or ["0"])[0])', 'offset = _intv(q, "offset", 0, 0, 1_000_000)'), ('hosts = max(5, min(120, int((q.get("hosts") or ["40"])[0])))', 'hosts = _intv(q, "hosts", 40, 5, 120)'), ('add = max(1, min(60, int((q.get("add") or ["25"])[0])))', 'add = _intv(q, "add", 25, 1, 60)'), ] for old, new in pairs: if old in s: s = s.replace(old, new) rep.append("int -> " + new.split(" = ")[0] + " " + new.split("_intv")[1][:26]) old_post = ''' def do_POST(self): u = urllib.parse.urlsplit(self.path) n = int(self.headers.get("Content-Length") or 0)''' new_post = ''' def do_POST(self): u = urllib.parse.urlsplit(self.path) if not _ui_auth(self): return self._json({"error": "unauthorized"}, 401) n = min(int(self.headers.get("Content-Length") or 0), 2_000_000)''' assert old_post in s, "do_POST шаблон не найден" s = s.replace(old_post, new_post) rep.append("POST: токен + лимит тела") s = s.replace(''' print(f"omni ui: http://127.0.0.1:{port} (источников {len(sources.all_sources())})")''', ''' print(f"omni ui: http://127.0.0.1:{port} (источников {len(sources.all_sources())})") if not UI_TOKEN: print("внимание: OMNI_UI_TOKEN не задан — POST /api/* без авторизации; наружу только за nginx с basic-auth")''') p.write_text(s, encoding="utf-8", newline="\n") print("\n".join(rep)) print("остались сырые int((q.get:", s.count("int((q.get"))