39 lines
1.4 KiB
Python
39 lines
1.4 KiB
Python
"""Authoritative image decoding and metadata removal for evidence uploads."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
|
|
from PIL import Image, ImageOps
|
|
|
|
|
|
MAX_IMAGE_PIXELS = 12 * 1024 * 1024
|
|
|
|
|
|
def sanitize_image(content_type: str, content: bytes) -> bytes:
|
|
"""Apply orientation and return metadata-free bytes in the claimed format."""
|
|
format_names = {
|
|
"image/png": "PNG",
|
|
"image/jpeg": "JPEG",
|
|
"image/webp": "WEBP",
|
|
}
|
|
try:
|
|
with Image.open(io.BytesIO(content)) as source:
|
|
claimed_format = format_names[content_type]
|
|
if source.format != claimed_format:
|
|
raise ValueError("image content does not match the selected image type")
|
|
if getattr(source, "n_frames", 1) != 1:
|
|
raise ValueError("animated image evidence is not supported")
|
|
if source.width * source.height > MAX_IMAGE_PIXELS:
|
|
raise ValueError("attachment must not exceed 12 megapixels")
|
|
image = ImageOps.exif_transpose(source)
|
|
image.load()
|
|
output = io.BytesIO()
|
|
save_options = {"quality": 90} if claimed_format in {"JPEG", "WEBP"} else {}
|
|
image.save(output, format=claimed_format, **save_options)
|
|
return output.getvalue()
|
|
except ValueError:
|
|
raise
|
|
except (KeyError, OSError) as exc:
|
|
raise ValueError("attachment must be a valid static image") from exc
|