2026-06-04 01:32:50 +02:00
|
|
|
"""Small helpers for route-local upload size caps."""
|
|
|
|
|
|
2026-06-07 20:42:24 +00:00
|
|
|
import os
|
|
|
|
|
|
2026-06-04 01:32:50 +02:00
|
|
|
from fastapi import HTTPException, UploadFile
|
|
|
|
|
|
2026-06-07 20:42:24 +00:00
|
|
|
DEFAULT_CHAT_UPLOAD_MAX_BYTES = 10 * 1024 * 1024
|
|
|
|
|
CHAT_UPLOAD_MAX_BYTES_ENV = "ODYSSEUS_CHAT_UPLOAD_MAX_BYTES"
|
|
|
|
|
|
2026-06-04 01:32:50 +02:00
|
|
|
|
|
|
|
|
def format_byte_limit(limit: int) -> str:
|
|
|
|
|
if limit % (1024 * 1024) == 0:
|
|
|
|
|
return f"{limit // (1024 * 1024)} MB"
|
|
|
|
|
if limit % 1024 == 0:
|
|
|
|
|
return f"{limit // 1024} KB"
|
|
|
|
|
return f"{limit} bytes"
|
|
|
|
|
|
|
|
|
|
|
2026-06-07 20:42:24 +00:00
|
|
|
def read_byte_limit_env(name: str, default: int) -> int:
|
|
|
|
|
raw = os.getenv(name)
|
|
|
|
|
if raw is None or not raw.strip():
|
|
|
|
|
return default
|
|
|
|
|
try:
|
|
|
|
|
limit = int(raw)
|
|
|
|
|
except ValueError as exc:
|
|
|
|
|
raise ValueError(f"{name} must be an integer byte count") from exc
|
|
|
|
|
if limit < 1:
|
|
|
|
|
raise ValueError(f"{name} must be greater than 0")
|
|
|
|
|
return limit
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_chat_upload_max_bytes() -> int:
|
|
|
|
|
return read_byte_limit_env(CHAT_UPLOAD_MAX_BYTES_ENV, DEFAULT_CHAT_UPLOAD_MAX_BYTES)
|
|
|
|
|
|
|
|
|
|
|
refactor(uploads): centralize upload byte-limits in upload_limits.py (#3364) (#3518)
Move every per-route upload byte-limit into src/upload_limits.py as a
validated, env-overridable constant via read_byte_limit_env:
- Add GALLERY_UPLOAD_MAX_BYTES, GALLERY_TRANSFORM_UPLOAD_MAX_BYTES,
MEMORY_IMPORT_MAX_BYTES, PERSONAL_UPLOAD_MAX_BYTES,
EMAIL_COMPOSE_UPLOAD_MAX_BYTES, STT_MAX_AUDIO_BYTES, ICS_MAX_BYTES.
- Routes import their constant instead of defining it locally: replaces 4
raw int(os.getenv(...)) and removes 3 hardcoded literals.
- The 3 previously-hardcoded limits (email compose, STT audio, calendar
ICS) are now env-overridable with the same ODYSSEUS_*_MAX_BYTES naming.
- Defaults unchanged, so behavior is unchanged unless an env var is set;
an invalid value now fails fast with a clear message instead of a bare
int() ValueError.
- Document all env vars in .env.example and the README.
Fixes #3364
2026-06-09 01:24:30 +02:00
|
|
|
# Per-route upload byte-limits, single-sourced here (issue #3364). Each is
|
|
|
|
|
# validated + env-overridable via read_byte_limit_env: set the matching
|
|
|
|
|
# ODYSSEUS_*_MAX_BYTES env var to an integer byte count to tune it; an invalid
|
|
|
|
|
# value fails fast at import rather than crashing mid-request. Defaults match
|
|
|
|
|
# the prior per-route values, so behavior is unchanged unless an env var is set.
|
|
|
|
|
GALLERY_UPLOAD_MAX_BYTES = read_byte_limit_env(
|
|
|
|
|
"ODYSSEUS_GALLERY_UPLOAD_MAX_BYTES", 100 * 1024 * 1024
|
|
|
|
|
)
|
|
|
|
|
GALLERY_TRANSFORM_UPLOAD_MAX_BYTES = read_byte_limit_env(
|
|
|
|
|
"ODYSSEUS_GALLERY_TRANSFORM_UPLOAD_MAX_BYTES", 25 * 1024 * 1024
|
|
|
|
|
)
|
|
|
|
|
MEMORY_IMPORT_MAX_BYTES = read_byte_limit_env(
|
|
|
|
|
"ODYSSEUS_MEMORY_IMPORT_MAX_BYTES", 10 * 1024 * 1024
|
|
|
|
|
)
|
|
|
|
|
PERSONAL_UPLOAD_MAX_BYTES = read_byte_limit_env(
|
|
|
|
|
"ODYSSEUS_PERSONAL_UPLOAD_MAX_BYTES", 25 * 1024 * 1024
|
|
|
|
|
)
|
|
|
|
|
EMAIL_COMPOSE_UPLOAD_MAX_BYTES = read_byte_limit_env(
|
|
|
|
|
"ODYSSEUS_EMAIL_COMPOSE_UPLOAD_MAX_BYTES", 25 * 1024 * 1024
|
|
|
|
|
)
|
|
|
|
|
STT_MAX_AUDIO_BYTES = read_byte_limit_env(
|
|
|
|
|
"ODYSSEUS_STT_MAX_AUDIO_BYTES", 25 * 1024 * 1024
|
|
|
|
|
)
|
|
|
|
|
ICS_MAX_BYTES = read_byte_limit_env(
|
|
|
|
|
"ODYSSEUS_ICS_MAX_BYTES", 10 * 1024 * 1024
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-06-04 01:32:50 +02:00
|
|
|
async def read_upload_limited(upload: UploadFile, limit: int, label: str = "Upload") -> bytes:
|
|
|
|
|
"""Read an UploadFile with a hard byte cap."""
|
|
|
|
|
data = await upload.read(limit + 1)
|
|
|
|
|
if len(data) > limit:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=413,
|
|
|
|
|
detail=f"{label} exceeds {format_byte_limit(limit)} limit",
|
|
|
|
|
)
|
|
|
|
|
return data
|