# Safe Extract ZIP

Fuente conceptual: OpenViking `openviking/utils/zip_safe.py` (AGPL-3.0)
Reimplementar desde cero. No copiar codigo.

## Funciones a implementar

### 1. safe_extract_zip

- **Dominio:** infra
- **Lang:** Python
- **Purity:** impure (I/O: escribe archivos)
- **Signature:** `safe_extract_zip(zip_path: str, dest_dir: str) -> None`
- **Descripcion:** Extrae un archivo ZIP con proteccion contra Zip Slip (path traversal attack). Valida que cada archivo extraido quede dentro del directorio destino.
- **Algoritmo:**
  1. Resolver dest_dir a path absoluto
  2. Normalizar filenames del ZIP (reparar encoding UTF-8)
  3. Para cada miembro del ZIP:
     - Resolver path completo: `(dest_dir / member.filename).resolve()`
     - Verificar que `str(member_path).startswith(str(dest_dir) + os.sep)`
     - Si no: raise ValueError "Zip Slip attempt detected"
     - Si si: extraer
- **Deps:** `zipfile`, `pathlib`, `os`
- **Error type:** ValueError (zip slip), zipfile.BadZipFile
- **Tests:** ZIP normal, ZIP con path traversal (`../../etc/passwd`), ZIP con paths absolutos

### 2. normalize_zip_filenames

- **Dominio:** infra
- **Lang:** Python
- **Purity:** impure (modifica objeto ZipFile in-place)
- **Signature:** `normalize_zip_filenames(zipf: zipfile.ZipFile) -> None`
- **Descripcion:** Repara nombres de archivos UTF-8 en ZIPs que no tienen el flag UTF-8 seteado. Comun en archivos creados en Windows con nombres CJK.
- **Algoritmo:**
  1. Para cada miembro sin flag UTF-8 (bit 0x800):
     - Intentar: encode como CP437 → decode como UTF-8
     - Si el resultado tiene CJK y el original tiene mojibake: reparar
     - Si no: dejar como esta
  2. Si se reparo algun nombre: setear `zipf.metadata_encoding = "utf-8"`
- **Deteccion de CJK:** chars en rangos `\u3400-\u4dbf`, `\u4e00-\u9fff`, `\u3000-\u303f`, `\uff00-\uffef`
- **Deteccion de mojibake:** chars en rangos Greek (`\u0370-\u03ff`), Math (`\u2200-\u22ff`), Box Drawing (`\u2500-\u257f`)
- **Deps:** `zipfile`
- **Tests:** ZIP con nombres UTF-8 correctos (no cambiar), ZIP con nombres CJK mojibake (reparar)
