85 lines
3.7 KiB
Python
85 lines
3.7 KiB
Python
"""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")) |