diff --git a/links/file_views.py b/links/file_views.py index 2a03253..c9de923 100644 --- a/links/file_views.py +++ b/links/file_views.py @@ -74,7 +74,15 @@ def _save_uploaded_file(f): os.rename(tmp_path, dest_path) - mime_type = f.content_type or mimetypes.guess_type(f.name)[0] or 'application/octet-stream' + guessed = mimetypes.guess_type(f.name)[0] + # Clients (notably the iOS uploader) often send a generic + # "application/octet-stream" Content-Type even for media files. When that + # happens, prefer the type inferred from the filename extension so the + # file is previewable/streamable in the UI. + if f.content_type and f.content_type != 'application/octet-stream': + mime_type = f.content_type + else: + mime_type = guessed or f.content_type or 'application/octet-stream' return FileUpload.objects.create( name=f.name, stored_name=stored_name, diff --git a/tests/test_files_api.py b/tests/test_files_api.py index 094bbf4..c949505 100644 --- a/tests/test_files_api.py +++ b/tests/test_files_api.py @@ -27,6 +27,21 @@ def _pdf(): return SimpleUploadedFile("document.pdf", data, content_type="application/pdf") +def _mp4_generic_content_type(): + """An .mp4 upload whose client sent a generic Content-Type. + + Reproduces the iOS-uploader behaviour that caused mb4 files to be + persisted as "application/octet-stream" and therefore un-previewable + in the UI. The server must infer "video/mp4" from the filename instead. + """ + # minimal mp4 box header — content is irrelevant, only the extension + # and Content-Type matter for the mime-type inference path. + data = b"\x00\x00\x00\x20ftypisom\x00\x00\x02\x00isomiso2avc1mp41" + return SimpleUploadedFile( + "clip.mp4", data, content_type="application/octet-stream" + ) + + @pytest.mark.django_db class TestFilesAPI: BASE = "/api/files" @@ -171,3 +186,21 @@ class TestFilesAPI: ).json()[0] assert "formatted_size" in uploaded assert "B" in uploaded["formatted_size"] + + def test_upload_infers_mime_from_filename_when_client_sends_generic(self, api_client): + """An .mp4 uploaded with Content-Type=application/octet-stream must + still be persisted as video/mp4 so the UI can render a