Files
workdock-platform/backend/workflows/upload_validation.py
2026-03-27 12:44:53 +01:00

148 lines
4.9 KiB
Python

from __future__ import annotations
from pathlib import Path
from django import forms
from django.utils.translation import gettext as _
def _header_matches(extension: str, header: bytes) -> bool:
extension = extension.lower().lstrip(".")
if extension == "png":
return header.startswith(b"\x89PNG\r\n\x1a\n")
if extension in {"jpg", "jpeg"}:
return header.startswith(b"\xff\xd8\xff")
if extension == "webp":
return header.startswith(b"RIFF") and header[8:12] == b"WEBP"
if extension == "pdf":
return header.startswith(b"%PDF")
if extension == "ico":
return header.startswith(b"\x00\x00\x01\x00")
if extension == "svg":
text = header.decode("utf-8", errors="ignore").lower()
return "<svg" in text
return False
def validate_uploaded_file(
uploaded_file,
*,
allowed_extensions: set[str],
max_size_bytes: int,
allowed_content_types: set[str] | None = None,
invalid_type_message: str,
size_message: str,
unreadable_message: str,
) -> None:
if not uploaded_file:
return
if getattr(uploaded_file, "size", 0) > max_size_bytes:
raise forms.ValidationError(size_message)
extension = Path(getattr(uploaded_file, "name", "")).suffix.lower().lstrip(".")
if extension not in allowed_extensions:
raise forms.ValidationError(invalid_type_message)
content_type = (getattr(uploaded_file, "content_type", "") or "").lower().strip()
if allowed_content_types and content_type and content_type not in allowed_content_types:
raise forms.ValidationError(invalid_type_message)
try:
header = uploaded_file.read(512)
uploaded_file.seek(0)
except Exception as exc:
raise forms.ValidationError(unreadable_message) from exc
if not _header_matches(extension, header):
raise forms.ValidationError(invalid_type_message)
def validate_avatar_upload(uploaded_file) -> None:
validate_uploaded_file(
uploaded_file,
allowed_extensions={"png", "jpg", "jpeg", "webp", "svg"},
max_size_bytes=5 * 1024 * 1024,
allowed_content_types={
"image/png",
"image/x-png",
"image/jpeg",
"image/jpg",
"image/pjpeg",
"image/webp",
"image/svg+xml",
},
invalid_type_message=_("Bitte ein PNG-, JPG-, WEBP- oder SVG-Bild hochladen."),
size_message=_("Das Profilbild darf maximal 5 MB groß sein."),
unreadable_message=_("Die Bilddatei konnte nicht gelesen werden."),
)
def validate_logo_upload(uploaded_file) -> None:
validate_uploaded_file(
uploaded_file,
allowed_extensions={"svg", "png", "jpg", "jpeg", "webp"},
max_size_bytes=5 * 1024 * 1024,
allowed_content_types={
"image/png",
"image/x-png",
"image/jpeg",
"image/jpg",
"image/pjpeg",
"image/webp",
"image/svg+xml",
},
invalid_type_message=_("Bitte ein SVG-, PNG-, JPG- oder WEBP-Bild hochladen."),
size_message=_("Das Logo darf maximal 5 MB groß sein."),
unreadable_message=_("Die Logo-Datei konnte nicht gelesen werden."),
)
def validate_favicon_upload(uploaded_file) -> None:
validate_uploaded_file(
uploaded_file,
allowed_extensions={"ico", "png", "svg", "webp"},
max_size_bytes=2 * 1024 * 1024,
allowed_content_types={
"image/x-icon",
"image/vnd.microsoft.icon",
"image/png",
"image/x-png",
"image/webp",
"image/svg+xml",
},
invalid_type_message=_("Bitte eine ICO-, PNG-, SVG- oder WEBP-Datei hochladen."),
size_message=_("Das Favicon darf maximal 2 MB groß sein."),
unreadable_message=_("Die Favicon-Datei konnte nicht gelesen werden."),
)
def validate_pdf_upload(uploaded_file) -> None:
validate_uploaded_file(
uploaded_file,
allowed_extensions={"pdf"},
max_size_bytes=10 * 1024 * 1024,
allowed_content_types={"application/pdf"},
invalid_type_message=_("Bitte eine gültige PDF-Datei hochladen."),
size_message=_("Der PDF-Briefkopf darf maximal 10 MB groß sein."),
unreadable_message=_("Die PDF-Datei konnte nicht gelesen werden."),
)
def validate_signature_upload(uploaded_file) -> None:
validate_uploaded_file(
uploaded_file,
allowed_extensions={"png", "jpg", "jpeg"},
max_size_bytes=4 * 1024 * 1024,
allowed_content_types={
"image/png",
"image/x-png",
"image/jpeg",
"image/jpg",
"image/pjpeg",
},
invalid_type_message=_("Bitte eine PNG- oder JPG-Datei hochladen."),
size_message=_("Die Signatur-Datei ist zu groß (max. 4 MB)."),
unreadable_message=_("Die Signatur-Datei konnte nicht gelesen werden."),
)