Когда пользователь выбирает фотографию, всё выглядит просто: браузер отправляет файл,
а сервер сохраняет его. Но имя, заявленный тип и байты прислал сам клиент — им нельзя
автоматически доверять.
Безопасная загрузка — это не одна сложная проверка, а несколько понятных фильтров.
Сначала проверь файл, затем придумай ему безопасное имя и только после этого передавай
в локальное или облачное хранилище.
Многоуровневая проверка
Представь пропускной пункт: каждая проверка отвечает только на один вопрос. Такой код
проще читать, тестировать и использовать в другом проекте.
Для изображения проверь по порядку:
- файл действительно передан;
- заявленный MIME входит в allowlist;
- число прочитанных байтов не превышает лимит;
- сигнатура и декодирование подтверждают формат;
- ключ хранения создан сервером, а не взят из
filename.
filename="../secret.txt" нельзя соединять с каталогом загрузок. Надёжный ключ:
from uuid import uuid4
key = f"images/{uuid4().hex}.webp"
FastAPI UploadFile
Для UploadFile нужен python-multipart. Он предоставляет метаданные и файловый
интерфейс, а большие данные могут временно переноситься из памяти на диск.
from fastapi import HTTPException, UploadFile
MAX_BYTES = 5 * 1024 * 1024
ALLOWED_TYPES = {"image/jpeg", "image/png", "image/webp"}
async def read_limited(upload: UploadFile) -> bytes:
if upload.content_type not in ALLOWED_TYPES:
raise HTTPException(415, "Unsupported image type")
data = await upload.read(MAX_BYTES + 1)
if len(data) > MAX_BYTES:
raise HTTPException(413, "Image is too large")
if not data:
raise HTTPException(400, "Image is empty")
return data
Это только первые проверки. После чтения проверь magic bytes или открой изображение
библиотекой, которая действительно декодирует формат. Никогда не доверяй одному
расширению.
Дополнительный байт в MAX_BYTES + 1 помогает заметить превышение, не читая файл
целиком. Для очень больших файлов применяют чтение небольшими порциями.
Где хранить
- загрузки держат вне каталогов шаблонов и исполняемого кода;
- для публичных файлов разрешают только ожидаемый способ чтения;
- для приватных файлов проверяют пользователя перед выдачей;
- при удалении записи удаляют объект по сохранённому ключу;
- лимит тела запроса полезно дублировать на reverse proxy.
Тесты
Обязательно проверь корректный файл, пустой файл, неверный MIME, верный MIME с
неверными байтами, превышение размера и имя ../cover.png.
Начни с успешного случая, затем меняй ровно одно условие за тест. Ответы 415 для
типа, 413 для размера и 400 для пустого файла понятно объясняют причину отказа.
💬 Комментарии (0)
Комментариев пока нет
Станьте первым, кто поделится мнением об этой статье!