Spaces:
Running
Running
| from __future__ import annotations | |
| import hashlib | |
| import mimetypes | |
| from pathlib import Path | |
| from typing import Any | |
| from uuid import uuid4 | |
| from fastapi import HTTPException, UploadFile | |
| from PIL import Image, ImageOps | |
| from app.config import settings | |
| EXTENSION_TO_FORMAT = { | |
| "jpg": "JPEG", | |
| "jpeg": "JPEG", | |
| "png": "PNG", | |
| "webp": "WEBP", | |
| } | |
| def new_verification_id() -> str: | |
| return str(uuid4()) | |
| def validate_upload_metadata(file: UploadFile) -> str: | |
| if not file.filename: | |
| raise HTTPException(status_code=400, detail="Uploaded file must have a filename.") | |
| suffix = Path(file.filename).suffix.lower().lstrip(".") | |
| if suffix not in settings.allowed_extensions: | |
| raise HTTPException(status_code=415, detail=f"Unsupported image type: {suffix or 'unknown'}") | |
| return suffix | |
| def save_upload(file: UploadFile, verification_id: str, suffix: str) -> Path: | |
| target = settings.uploads_dir / f"{verification_id}.{suffix}" | |
| size = 0 | |
| with target.open("wb") as out: | |
| while chunk := file.file.read(1024 * 1024): | |
| size += len(chunk) | |
| if size > settings.max_upload_bytes: | |
| target.unlink(missing_ok=True) | |
| raise HTTPException(status_code=413, detail="Uploaded image exceeds size limit.") | |
| out.write(chunk) | |
| file.file.seek(0) | |
| return target | |
| def sha256_file(path: Path) -> str: | |
| digest = hashlib.sha256() | |
| with path.open("rb") as f: | |
| for chunk in iter(lambda: f.read(1024 * 1024), b""): | |
| digest.update(chunk) | |
| return digest.hexdigest() | |
| def validate_image_file(path: Path, original_filename: str, suffix: str) -> dict[str, Any]: | |
| try: | |
| with Image.open(path) as img: | |
| img.verify() | |
| with Image.open(path) as img: | |
| transposed = ImageOps.exif_transpose(img) | |
| width, height = transposed.size | |
| mode = transposed.mode | |
| fmt = img.format | |
| except Exception as exc: | |
| path.unlink(missing_ok=True) | |
| raise HTTPException(status_code=400, detail=f"Invalid or unreadable image: {exc}") from exc | |
| expected = EXTENSION_TO_FORMAT.get(suffix) | |
| if fmt not in set(EXTENSION_TO_FORMAT.values()) or (expected and fmt != expected): | |
| raise HTTPException(status_code=400, detail=f"Uploaded file content is not a valid {suffix} image.") | |
| mime_type = mimetypes.guess_type(original_filename)[0] or Image.MIME.get(fmt) or "application/octet-stream" | |
| return { | |
| "filename": original_filename, | |
| "sha256": sha256_file(path), | |
| "width": int(width), | |
| "height": int(height), | |
| "format": fmt, | |
| "mode": mode, | |
| "mime_type": mime_type, | |
| "file_size_bytes": path.stat().st_size, | |
| } | |