Temporarily disable CSV uploads
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -40,7 +40,7 @@ The browser sends files one at a time: it waits for the WebDAV `PUT`, then asks
|
|||||||
- `upload/frontend/upload/put_to_vm.js` sends a raw file body with `PUT` and reports progress.
|
- `upload/frontend/upload/put_to_vm.js` sends a raw file body with `PUT` and reports progress.
|
||||||
- `upload/frontend/upload/upload_via_vm.js` coordinates the per-file `PUT` and `/api/upload_refs` requests.
|
- `upload/frontend/upload/upload_via_vm.js` coordinates the per-file `PUT` and `/api/upload_refs` requests.
|
||||||
|
|
||||||
The UI accepts `.pdf`, `.doc`, `.docx`, `.txt`, `.md`, `.csv`, and `.zip` inputs. ZIP archives are expanded by the browser picker, and their allowed inner files are uploaded individually. The backend `drhider/extractor.py` also expands ZIPs received directly (for example, through `POST /api/upload`). Per-file (50 MiB) and per-session (500 MiB) limits are enforced by the backend; the picker's entry/size/depth limits apply only while unpacking ZIP archives.
|
The UI accepts `.pdf`, `.doc`, `.docx`, `.txt`, `.md`, and `.zip` inputs. CSV input is temporarily disabled in the picker and rejected by both upload endpoints; CSV files inside ZIP archives are not extracted. The CSV parsing and obfuscation code remains in `drhider/extractor.py` for internal use and possible re-enabling after reliability work on the other formats. This does not affect the separate mapping CSV generated as an output for each processing session. ZIP archives are expanded by the browser picker, and their allowed inner files are uploaded individually. The backend `drhider/extractor.py` also expands ZIPs received directly (for example, through `POST /api/upload`). Per-file (50 MiB) and per-session (500 MiB) limits are enforced by the backend; the picker's entry/size/depth limits apply only while unpacking ZIP archives.
|
||||||
|
|
||||||
### Flask composition and routes
|
### Flask composition and routes
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ Main endpoints:
|
|||||||
|
|
||||||
The core package is split by responsibility:
|
The core package is split by responsibility:
|
||||||
|
|
||||||
- `drhider/extractor.py` converts `.docx`, `.pdf`, and `.doc` content to text/Markdown, extracts CSV cell values for scanning, obfuscates CSV cells, and expands ZIP archives with ZIP-bomb protections. `.doc` conversion calls the configured conversion service.
|
- `drhider/extractor.py` converts `.docx`, `.pdf`, and `.doc` content to text/Markdown, expands ZIP archives with ZIP-bomb protections, and retains internal CSV extraction/obfuscation code. CSV input is temporarily disabled at upload entry points while reliability work focuses on other formats; the separate output mapping CSV remains enabled. `.doc` conversion calls the configured conversion service.
|
||||||
- `drhider/scanner.py` detects entities with regular expressions and LLM-based NER, building the original-to-replacement mapping.
|
- `drhider/scanner.py` detects entities with regular expressions and LLM-based NER, building the original-to-replacement mapping.
|
||||||
- `drhider/replacer.py` applies that mapping to extracted text.
|
- `drhider/replacer.py` applies that mapping to extracted text.
|
||||||
- `drhider/builder.py` builds the obfuscated ZIP and mapping CSV. The mapping CSV is deliberately not included inside the ZIP; it is downloaded through its separate endpoint.
|
- `drhider/builder.py` builds the obfuscated ZIP and mapping CSV. The mapping CSV is deliberately not included inside the ZIP; it is downloaded through its separate endpoint.
|
||||||
|
|||||||
+18
-3
@@ -230,7 +230,13 @@ def detect_delimiter(text: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def csv_to_text(content: bytes) -> str:
|
def csv_to_text(content: bytes) -> str:
|
||||||
"""Распарсить CSV и склеить значения ячеек построчно для сканирования."""
|
"""Распарсить CSV и склеить значения ячеек построчно для сканирования.
|
||||||
|
|
||||||
|
Входная загрузка CSV временно отключена, чтобы сосредоточить тестирование
|
||||||
|
надёжности на остальных форматах. Функция оставлена для внутренней
|
||||||
|
обработки и будущего повторного включения поддержки; не удалять её
|
||||||
|
и связанную с ней логику вместе с UI/API-ограничением загрузки.
|
||||||
|
"""
|
||||||
text = decode_text(content).removeprefix("\ufeff")
|
text = decode_text(content).removeprefix("\ufeff")
|
||||||
delimiter = detect_delimiter(text)
|
delimiter = detect_delimiter(text)
|
||||||
reader = csv.reader(
|
reader = csv.reader(
|
||||||
@@ -246,7 +252,13 @@ def csv_to_text(content: bytes) -> str:
|
|||||||
|
|
||||||
def obfuscate_csv(content: bytes, mapping: Dict[str, str],
|
def obfuscate_csv(content: bytes, mapping: Dict[str, str],
|
||||||
sorted_keys: List[str], compiled_re) -> bytes:
|
sorted_keys: List[str], compiled_re) -> bytes:
|
||||||
"""Заменить сущности в CSV-ячейках, сохранив порядок строк и столбцов."""
|
"""Заменить сущности в CSV-ячейках, сохранив порядок строк и столбцов.
|
||||||
|
|
||||||
|
Входная загрузка CSV временно отключена для фокусировки на надёжности
|
||||||
|
остальных форматов. Обработчик сохраняется для внутреннего использования
|
||||||
|
и будущего повторного включения; CSV-файл mapping, выдаваемый как результат,
|
||||||
|
является отдельной функцией и этой временной блокировкой не затрагивается.
|
||||||
|
"""
|
||||||
text = decode_text(content).removeprefix("\ufeff")
|
text = decode_text(content).removeprefix("\ufeff")
|
||||||
delimiter = detect_delimiter(text)
|
delimiter = detect_delimiter(text)
|
||||||
reader = csv.reader(
|
reader = csv.reader(
|
||||||
@@ -417,7 +429,10 @@ def expand_zips(files: List[Tuple[str, bytes, str]]) -> List[Tuple[str, bytes, s
|
|||||||
continue
|
continue
|
||||||
# Синхронизация с фронтом (listZipFiles): из zip берём ТОЛЬКО документы
|
# Синхронизация с фронтом (listZipFiles): из zip берём ТОЛЬКО документы
|
||||||
low = name.lower()
|
low = name.lower()
|
||||||
if not (low.endswith('.zip') or low.endswith(('.pdf', '.doc', '.docx', '.txt', '.md', '.csv'))):
|
# CSV-процессор сохранён, но пользовательский ввод CSV
|
||||||
|
# временно отключён. Не извлекаем CSV из ZIP, чтобы обход
|
||||||
|
# ограничения через архив не включал эту ветку обработки.
|
||||||
|
if not (low.endswith('.zip') or low.endswith(('.pdf', '.doc', '.docx', '.txt', '.md'))):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
inner_data = zf.read(info)
|
inner_data = zf.read(info)
|
||||||
|
|||||||
+1
-2
@@ -22,7 +22,7 @@ if _sys_path_root not in sys.path:
|
|||||||
sys.path.insert(0, _sys_path_root)
|
sys.path.insert(0, _sys_path_root)
|
||||||
|
|
||||||
# Версия приложения (меняется при изменениях)
|
# Версия приложения (меняется при изменениях)
|
||||||
VERSION = "1.0.3"
|
VERSION = "1.0.4"
|
||||||
|
|
||||||
|
|
||||||
def setup_logging():
|
def setup_logging():
|
||||||
@@ -98,4 +98,3 @@ app = create_app()
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from waitress import serve
|
from waitress import serve
|
||||||
serve(app, host="0.0.0.0", port=5000, threads=8)
|
serve(app, host="0.0.0.0", port=5000, threads=8)
|
||||||
|
|
||||||
|
|||||||
@@ -42,13 +42,20 @@ def _disconnect_exceptions():
|
|||||||
def upload():
|
def upload():
|
||||||
"""Upload files to session (один или несколько)."""
|
"""Upload files to session (один или несколько)."""
|
||||||
sid = request.form.get("session", "")
|
sid = request.form.get("session", "")
|
||||||
if not sid:
|
|
||||||
sid = create_session()
|
|
||||||
uploaded = request.files.getlist("files")
|
uploaded = request.files.getlist("files")
|
||||||
if not uploaded:
|
if not uploaded:
|
||||||
log.warning("upload: no files, sid=%s", sid)
|
log.warning("upload: no files, sid=%s", sid)
|
||||||
return jsonify({"ok": False, "error": "No file"}), 400
|
return jsonify({"ok": False, "error": "No file"}), 400
|
||||||
|
|
||||||
|
# CSV input is temporarily disabled so reliability testing can focus on the
|
||||||
|
# other document formats. Keep the CSV processing code in the core intact.
|
||||||
|
if any(safe_name(f.filename).lower().endswith(".csv") for f in uploaded):
|
||||||
|
log.warning("upload: CSV input is disabled")
|
||||||
|
return jsonify({"ok": False, "error": "CSV uploads are temporarily disabled"}), 400
|
||||||
|
|
||||||
|
if not sid:
|
||||||
|
sid = create_session()
|
||||||
|
|
||||||
added = 0
|
added = 0
|
||||||
had_unnamed = False
|
had_unnamed = False
|
||||||
for f in uploaded:
|
for f in uploaded:
|
||||||
|
|||||||
@@ -457,7 +457,7 @@
|
|||||||
<div class="card-header">Обфускация документов</div>
|
<div class="card-header">Обфускация документов</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="sub">
|
<p class="sub">
|
||||||
Загрузите документы (.docx, .doc, .pdf, .txt, .md, .csv, .zip) — получите ZIP с обезличенными копиями.<br>
|
Загрузите документы (.docx, .doc, .pdf, .txt, .md, .zip) — получите ZIP с обезличенными копиями.<br>
|
||||||
CSV с таблицей замен скачивается отдельно.<br>
|
CSV с таблицей замен скачивается отдельно.<br>
|
||||||
<span style="color:#c0392b;">Ограничения: один файл не более 50 МБ, суммарно не более 500 МБ. Файлы сверх
|
<span style="color:#c0392b;">Ограничения: один файл не более 50 МБ, суммарно не более 500 МБ. Файлы сверх
|
||||||
лимитов помечаются статусом «пропущен» и не участвуют в обфускации. Пустые, повреждённые файлы и сканы без
|
лимитов помечаются статусом «пропущен» и не участвуют в обфускации. Пустые, повреждённые файлы и сканы без
|
||||||
@@ -507,7 +507,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<script src="/file-picker/file-picker.iife.js?v={{ version }}"></script>
|
<script src="/file-picker/file-picker.iife.js?v={{ version }}"></script>
|
||||||
<script>
|
<script>
|
||||||
const DOC_EXTS = ['.pdf', '.doc', '.docx', '.txt', '.md', '.csv'];
|
const DOC_EXTS = ['.pdf', '.doc', '.docx', '.txt', '.md'];
|
||||||
const ub = document.getElementById('uploadBtn');
|
const ub = document.getElementById('uploadBtn');
|
||||||
const cb = document.getElementById('cancelBtn');
|
const cb = document.getElementById('cancelBtn');
|
||||||
const nb = document.getElementById('newSessionBtn');
|
const nb = document.getElementById('newSessionBtn');
|
||||||
@@ -932,7 +932,7 @@
|
|||||||
onclick="event.stopPropagation()">
|
onclick="event.stopPropagation()">
|
||||||
<h3 style="margin-bottom:12px">Obfuscator — обфускация документов</h3>
|
<h3 style="margin-bottom:12px">Obfuscator — обфускация документов</h3>
|
||||||
<p style="font-size:13px;color:var(--muted);line-height:1.6">
|
<p style="font-size:13px;color:var(--muted);line-height:1.6">
|
||||||
Загрузите документы (.docx, .doc, .pdf, .txt, .md, .csv, .zip) — получите ZIP-архив с обезличенными копиями.
|
Загрузите документы (.docx, .doc, .pdf, .txt, .md, .zip) — получите ZIP-архив с обезличенными копиями.
|
||||||
Персональные данные, реквизиты компаний, телефоны, email и другие приватные сведения заменяются на обезличенные
|
Персональные данные, реквизиты компаний, телефоны, email и другие приватные сведения заменяются на обезличенные
|
||||||
коды.
|
коды.
|
||||||
CSV-файл с таблицей соответствия оригинал→замена скачивается отдельно.
|
CSV-файл с таблицей соответствия оригинал→замена скачивается отдельно.
|
||||||
|
|||||||
@@ -74,6 +74,12 @@ def create_upload_refs_blueprint(cfg: dict = None) -> Blueprint:
|
|||||||
if not refs:
|
if not refs:
|
||||||
log.warning("upload_refs: no files, sid=%s", sid)
|
log.warning("upload_refs: no files, sid=%s", sid)
|
||||||
return jsonify({"ok": False, "error": "No files"}), 400
|
return jsonify({"ok": False, "error": "No files"}), 400
|
||||||
|
# Reject CSV refs before pulling any objects. The browser picker also
|
||||||
|
# excludes CSV; this guard covers direct callers of the upload bridge.
|
||||||
|
if any(safe_name(ref.get("name") or "").lower().endswith(".csv") for ref in refs):
|
||||||
|
log.warning("upload_refs: CSV input is disabled, sid=%s", sid)
|
||||||
|
return jsonify({"ok": False, "error": "CSV uploads are temporarily disabled"}), 400
|
||||||
|
|
||||||
added = 0
|
added = 0
|
||||||
transport = cfg.get("httpxTransport") or current_app.config.get("UPLOAD_HTTPX_TRANSPORT")
|
transport = cfg.get("httpxTransport") or current_app.config.get("UPLOAD_HTTPX_TRANSPORT")
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user