diff --git a/deploy/convert_server.py b/deploy/convert_server.py index d3179de..3f8fb23 100755 --- a/deploy/convert_server.py +++ b/deploy/convert_server.py @@ -113,8 +113,6 @@ class Handler(BaseHTTPRequestHandler): self._handle_api_prompts_delete() elif self.path == "/api/sync": self._handle_api_sync() - elif self.path == "/api/drhider": - self._handle_drhider() else: self.send_error(404) @@ -241,78 +239,6 @@ class Handler(BaseHTTPRequestHandler): # ── POST /api/drhider ──────────────────────────────────────────────── - def _handle_drhider(self): - """Обфускация: multipart files → ZIP.""" - from services.drhider import obfuscate_files - import cgi, io as io_mod - - length = int(self.headers.get("Content-Length", 0)) - raw = self.rfile.read(length) - ctype = self.headers.get("Content-Type", "") - - # Простой multipart приём - import cgi, re, io as io_mod - _, params = cgi.parse_header(ctype) - boundary = params.get("boundary", "") - if not boundary: - self._json({"ok": False, "error": "No boundary"}, 400) - return - # Ручной разбор multipart (надёжнее чем cgi.FieldStorage) - parts = raw.split(b"--" + boundary.encode()) - files = [] - for part in parts: - if b"Content-Disposition" not in part: - continue - try: - hdr_end = part.index(b"\r\n\r\n") - except ValueError: - continue - hdrs = part[:hdr_end].decode("latin-1", errors="replace") - body = part[hdr_end + 4:] - if body.endswith(b"\r\n"): - body = body[:-2] - m = re.search(r'filename="([^"]*)"', hdrs) - if not m: - continue - name = os.path.basename(m.group(1)) - if not name or ".." in name or "/" in name or "\\" in name: - continue - files.append((name, body, "")) - - if not files: - self._json({"ok": False, "error": "Нет файлов"}, 400) - return - - class _VMLLM: - def complete(self, prompt): - import httpx - key = os.environ.get("LLM_KEY") or os.environ.get("LLM_API_KEY", "") - r = httpx.post( - os.environ.get("LLM_URL", "https://api.aillm.ru/v1/chat/completions"), - json={"model": os.environ.get("LLM_MODEL", "gpt-oss-120b"), - "messages": [{"role": "user", "content": prompt}], - "max_tokens": 8000, "temperature": 0.1}, - headers={"Authorization": f"Bearer {key}", - "Content-Type": "application/json"}, - timeout=120 - ) - r.raise_for_status() - return r.json()["choices"][0]["message"]["content"] - - try: - zip_data, _csv = obfuscate_files(files, llm_client=_VMLLM()) - self.send_response(200) - self.send_header("Content-Type", "application/zip") - self.send_header("Content-Disposition", 'attachment; filename="drhider_output.zip"') - self.send_header("Content-Length", str(len(zip_data))) - self.send_header("Access-Control-Allow-Origin", "*") - self.end_headers() - self.wfile.write(zip_data) - except Exception as e: - import traceback - traceback.print_exc() - self._json({"ok": False, "error": str(e)}, 500) - # ── /api/groups ?batch=X ───────────────────────────────────────────── def _handle_api_groups(self, parsed): @@ -497,8 +423,19 @@ class Handler(BaseHTTPRequestHandler): def _handle_app_js(self): try: - fname = urlparse(self.path).path.lstrip("/").replace("static/", "") - with open(os.path.join(os.path.dirname(__file__), fname), "rb") as f: + base = os.path.realpath(os.path.dirname(__file__)) + rel = urlparse(self.path).path.lstrip("/") + if rel.startswith("static/"): + rel = rel[len("static/"):] + # only .js and .svg + if not (rel.endswith(".js") or rel.endswith(".svg")): + self.send_error(404) + return + target = os.path.realpath(os.path.join(base, rel)) + if not target.startswith(base + os.sep): + self.send_error(403) + return + with open(target, "rb") as f: js = f.read() self.send_response(200) self.send_header("Content-Type", "application/javascript; charset=utf-8") diff --git a/deploy/services/drhider.py b/deploy/services/drhider.py index 268cf02..f9955c4 100644 --- a/deploy/services/drhider.py +++ b/deploy/services/drhider.py @@ -6,11 +6,10 @@ DrHider — обфускация документов (двухпроходна Согласованность: одна и та же сущность во всех файлах → одно и то же фиктивное значение. """ -DRHIDER_VERSION = "1.2" +DRHIDER_VERSION = "1.3" import io import csv import re -import hashlib import random import string import zipfile @@ -154,7 +153,8 @@ def generate_ks(original: str) -> str: def generate_passport(original: str) -> str: - prefix = re.match(r'(?:паспорт|серия\s+номер)[\s:№]*', original, re.IGNORECASE).group(0) + m = re.match(r'(?:паспорт|серия\s+номер)[\s:№]*', original, re.IGNORECASE) + prefix = m.group(0) if m else "" return prefix + f"{random.randint(10,99)} {random.randint(10,99)} {_random_digits(6)}" diff --git a/deploy/services/drhider_server.py b/deploy/services/drhider_server.py index db91cb5..9364dca 100644 --- a/deploy/services/drhider_server.py +++ b/deploy/services/drhider_server.py @@ -3,9 +3,11 @@ from http.server import HTTPServer, BaseHTTPRequestHandler from socketserver import ThreadingMixIn, TCPServer from urllib.parse import urlparse -import json, re, os, sys, cgi, io as io_mod +import json, re, os, sys, cgi -DRHIDER_SERVER_VERSION = "1.0" +DRHIDER_SERVER_VERSION = "1.3" + +MAX_BODY = 200 * 1024 * 1024 # 200 MB # ── Загрузка .env ──────────────────────────────────────────────────────── _env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".env") @@ -51,6 +53,9 @@ class Handler(BaseHTTPRequestHandler): from drhider import obfuscate_files length = int(self.headers.get("Content-Length", 0)) + if length <= 0 or length > MAX_BODY: + self._json({"ok": False, "error": "Файл слишком большой"}, 413) + return raw = self.rfile.read(length) ctype = self.headers.get("Content-Type", "") diff --git a/site/services/drhider.py b/site/services/drhider.py index 268cf02..f9955c4 100644 --- a/site/services/drhider.py +++ b/site/services/drhider.py @@ -6,11 +6,10 @@ DrHider — обфускация документов (двухпроходна Согласованность: одна и та же сущность во всех файлах → одно и то же фиктивное значение. """ -DRHIDER_VERSION = "1.2" +DRHIDER_VERSION = "1.3" import io import csv import re -import hashlib import random import string import zipfile @@ -154,7 +153,8 @@ def generate_ks(original: str) -> str: def generate_passport(original: str) -> str: - prefix = re.match(r'(?:паспорт|серия\s+номер)[\s:№]*', original, re.IGNORECASE).group(0) + m = re.match(r'(?:паспорт|серия\s+номер)[\s:№]*', original, re.IGNORECASE) + prefix = m.group(0) if m else "" return prefix + f"{random.randint(10,99)} {random.randint(10,99)} {_random_digits(6)}"