diff --git a/src/sentry/models/eventattachment.py b/src/sentry/models/eventattachment.py index 350c934b5a47aa..525e50c2730826 100644 --- a/src/sentry/models/eventattachment.py +++ b/src/sentry/models/eventattachment.py @@ -247,7 +247,9 @@ def putfile(cls, project_id: int, attachment: CachedAttachment) -> PutfileResult def normalize_content_type(content_type: str | None, name: str) -> str: if content_type: - return content_type.split(";")[0].strip() + normalized = content_type.split(";")[0].strip() + if normalized.lower() != "application/octet-stream": + return normalized return mimetypes.guess_type(name)[0] or "application/octet-stream" diff --git a/tests/sentry/ingest/ingest_consumer/test_ingest_consumer_processing.py b/tests/sentry/ingest/ingest_consumer/test_ingest_consumer_processing.py index a5496aaa58ee1c..aedd8f7f04c228 100644 --- a/tests/sentry/ingest/ingest_consumer/test_ingest_consumer_processing.py +++ b/tests/sentry/ingest/ingest_consumer/test_ingest_consumer_processing.py @@ -523,15 +523,15 @@ def test_process_stored_attachment( @pytest.mark.parametrize( "attachment", [ - ([b"Hello ", b"World!"], "event.attachment", "application/octet-stream"), - ([b""], "event.attachment", "application/octet-stream"), - ([], "event.attachment", "application/octet-stream"), + ([b"Hello ", b"World!"], "event.attachment", "text/plain"), + ([b""], "event.attachment", "text/plain"), + ([], "event.attachment", "text/plain"), ( [b'{"rendering_system":"flutter","windows":[]}'], "event.view_hierarchy", "application/json", ), - (b"inline attachment", "event.attachment", "application/octet-stream"), + (b"inline attachment", "event.attachment", "text/plain"), ], ids=["basic", "zerolen", "nochunks", "view_hierarchy", "inline"], ) diff --git a/tests/sentry/models/test_eventattachment.py b/tests/sentry/models/test_eventattachment.py index e3a58a1fd4338c..c10fa6eb85be25 100644 --- a/tests/sentry/models/test_eventattachment.py +++ b/tests/sentry/models/test_eventattachment.py @@ -4,7 +4,7 @@ from unittest import mock from uuid import uuid4 -from sentry.models.eventattachment import EventAttachment +from sentry.models.eventattachment import EventAttachment, normalize_content_type from sentry.testutils.cases import TestCase @@ -48,3 +48,29 @@ def test_v2_delete_skips_objectstore_during_cleanup( mock_get_session.return_value.delete.assert_not_called() assert not EventAttachment.objects.filter(id=attachment.id).exists() + + +class NormalizeContentTypeTest(TestCase): + def test_returns_explicit_content_type(self): + assert normalize_content_type("image/png", "file.png") == "image/png" + + def test_strips_charset_from_content_type(self): + assert normalize_content_type("text/plain; charset=utf-8", "file.txt") == "text/plain" + + def test_infers_from_filename_when_none(self): + assert normalize_content_type(None, "screenshot.png") == "image/png" + + def test_infers_from_filename_when_octet_stream(self): + assert normalize_content_type("application/octet-stream", "screenshot.png") == "image/png" + + def test_falls_back_to_octet_stream_when_unrecognized(self): + assert normalize_content_type(None, "data.bin") == "application/octet-stream" + + def test_octet_stream_with_unrecognized_name(self): + assert ( + normalize_content_type("application/octet-stream", "noext") + == "application/octet-stream" + ) + + def test_octet_stream_case_insensitive(self): + assert normalize_content_type("Application/Octet-Stream", "screenshot.png") == "image/png"