Talent-Lens / serving /app /utils /upload.py
riezqidr's picture
feat: scaffold CV screener serving layer with ingestion pipeline, REST API routers, repositories, storage, parsing, security, and integration/unit test foundations
bc9d0f6
Raw
History Blame Contribute Delete
1.64 kB
"""Bounded file-upload reader.
Reads an ``UploadFile`` in chunks and raises early if the content exceeds the
configured ceiling. This prevents an attacker from forcing the server to buffer
an arbitrarily large payload into memory before validation rejects it.
"""
from __future__ import annotations
from fastapi import UploadFile
from app.config import Settings, get_settings
from app.exceptions import EmptyDocumentError, PayloadTooLargeError
CHUNK_SIZE = 64 * 1024 # 64 KiB per read
async def read_upload_bounded(
file: UploadFile,
settings: Settings | None = None,
) -> bytes:
"""Read an upload file with an enforced size ceiling.
The file is consumed in chunks. As soon as the cumulative size exceeds
``settings.max_upload_bytes`` the read is aborted and
:class:`PayloadTooLargeError` is raised — the remaining bytes are never
buffered.
Args:
file: The FastAPI upload file to read.
settings: Optional configuration override.
Returns:
The complete file content as bytes.
Raises:
EmptyDocumentError: If the file contains zero bytes.
PayloadTooLargeError: If the file exceeds the configured size ceiling.
"""
cfg = settings or get_settings()
max_bytes = cfg.max_upload_bytes
chunks: list[bytes] = []
total = 0
while True:
chunk = await file.read(CHUNK_SIZE)
if not chunk:
break
total += len(chunk)
if total > max_bytes:
raise PayloadTooLargeError()
chunks.append(chunk)
if total == 0:
raise EmptyDocumentError()
return b"".join(chunks)