fix(media): serve .zip archives through /api/media

MEDIA:/path/to/file.zip links always failed to open in chat. .zip was
absent from MIME_MAP, so it resolved to application/octet-stream, which
_SESSION_MEDIA_TOKEN_TYPES rejects — the browser rendered a download
link that the server then refused.

Map .zip to application/zip and add an _ARCHIVE_TYPES set to the session
MEDIA: token allow-list. Archives are deliberately kept out of
_INLINE_PREVIEW_TYPES and _INLINE_IMAGE_TYPES so they are always sent as
Content-Disposition: attachment and never rendered inline.
This commit is contained in:
carlotestor authored and nesquena-hermes committed 2026-09-17 20:56:30 +00:00
1 parent 06d28c08cb
commit 8c5ee95dab
3 files changed
+100 -1

No files matched your search

+1
View File
@@ -1046,6 +1046,7 @@ MIME_MAP = {
".ico": "image/x-icon",
".bmp": "image/bmp",
".pdf": "application/pdf",
".zip": "application/zip",
".json": "application/json",
".html": "text/html",
".htm": "text/html",
+6 -1
View File
@@ -20775,7 +20775,12 @@ def _handle_media(handler, parsed):
"video/mp4", "video/quicktime", "video/webm", "video/ogg",
"application/pdf",
}
_SESSION_MEDIA_TOKEN_TYPES = _INLINE_IMAGE_TYPES | _AUDIO_VIDEO_PDF_TYPES | {"text/html"}
# Archives are download-only: never added to the inline-preview sets below,
# so they always get Content-Disposition: attachment.
_ARCHIVE_TYPES = {"application/zip"}
_SESSION_MEDIA_TOKEN_TYPES = (
_INLINE_IMAGE_TYPES | _AUDIO_VIDEO_PDF_TYPES | _ARCHIVE_TYPES | {"text/html"}
)
session_media_allowed = _session_media_token_allows_path(
qs.get("session_id", [""])[0],
target,
+93
View File
@@ -0,0 +1,93 @@
"""Tests for zip archive delivery through /api/media.
Archives previously resolved to application/octet-stream (``.zip`` was absent
from ``MIME_MAP``) and were therefore rejected by the session MEDIA: token
allow-list, so a ``MEDIA:/path/to/file.zip`` link always failed to open.
Covers:
1. ``.zip`` maps to ``application/zip`` in MIME_MAP
2. ``application/zip`` is accepted by the session MEDIA: token allow-list
3. ``application/zip`` is NOT an inline-preview type (must download, never render)
"""
from __future__ import annotations
import pathlib
import re
import unittest
REPO_ROOT = pathlib.Path(__file__).parent.parent
ROUTES_PY = (REPO_ROOT / "api" / "routes.py").read_text(encoding="utf-8")
class TestZipMimeMapping(unittest.TestCase):
""".zip must resolve to a concrete archive type, not octet-stream."""
def test_zip_in_mime_map(self):
from api.config import MIME_MAP
self.assertEqual(
MIME_MAP.get(".zip"),
"application/zip",
".zip must map to application/zip so /api/media can serve archives",
)
def test_zip_not_octet_stream(self):
from api.config import MIME_MAP
self.assertNotEqual(
MIME_MAP.get(".zip", "application/octet-stream"),
"application/octet-stream",
"octet-stream is rejected by the MEDIA: token allow-list",
)
class TestZipTokenAllowList(unittest.TestCase):
"""The session MEDIA: token gate must accept archives."""
def test_archive_types_declared(self):
self.assertIn(
'_ARCHIVE_TYPES = {"application/zip"}',
ROUTES_PY,
"_ARCHIVE_TYPES must declare the servable archive types",
)
def test_archive_types_in_session_token_types(self):
match = re.search(
r"_SESSION_MEDIA_TOKEN_TYPES\s*=\s*\(?\s*(.+?)\)?\n\s*session_media_allowed",
ROUTES_PY,
re.S,
)
self.assertIsNotNone(
match, "could not locate the _SESSION_MEDIA_TOKEN_TYPES assignment"
)
self.assertIn(
"_ARCHIVE_TYPES",
match.group(1),
"_SESSION_MEDIA_TOKEN_TYPES must include _ARCHIVE_TYPES",
)
class TestZipIsDownloadOnly(unittest.TestCase):
"""Archives must never be offered for inline rendering."""
def test_archive_not_in_inline_preview_types(self):
match = re.search(r"_INLINE_PREVIEW_TYPES\s*=\s*(.+)", ROUTES_PY)
self.assertIsNotNone(match, "could not locate _INLINE_PREVIEW_TYPES")
self.assertNotIn(
"_ARCHIVE_TYPES",
match.group(1),
"archives must not be inline-previewable; they must download",
)
def test_archive_not_in_inline_image_types(self):
match = re.search(r"_INLINE_IMAGE_TYPES\s*=\s*\{(.+?)\}", ROUTES_PY, re.S)
self.assertIsNotNone(match, "could not locate _INLINE_IMAGE_TYPES")
self.assertNotIn(
"zip",
match.group(1),
"zip must not be treated as an inline image type",
)
if __name__ == "__main__":
unittest.main()