mirror of
https://github.com/zulip/zulip.git
synced 2025-11-09 16:37:23 +00:00
upload: Allow uploads to set charset.
Previously, the `user_file.content_type` only contained the MIME type of the uploaded file; no other parameters were included, meaning that a file a client specified as `content-type: text/plain; charset=big5` would be stored with an `Attachment.content_type` of `text/plain`. Re-construct the full content-type header from `content_type_extra`, which includes those parameters. We do not include a test because Django does not support specifying such parameters in the upload path.
This commit is contained in:
committed by
Tim Abbott
parent
edb5943d8b
commit
ae001dfa96
@@ -5,6 +5,7 @@ import re
|
||||
import unicodedata
|
||||
from collections.abc import Callable, Iterator
|
||||
from datetime import datetime
|
||||
from email.message import EmailMessage
|
||||
from typing import IO, Any
|
||||
from urllib.parse import unquote, urljoin
|
||||
|
||||
@@ -80,9 +81,9 @@ def get_file_info(user_file: UploadedFile) -> tuple[str, str]:
|
||||
uploaded_file_name = user_file.name
|
||||
assert uploaded_file_name is not None
|
||||
|
||||
content_type = user_file.content_type
|
||||
# It appears Django's UploadedFile.content_type defaults to an empty string,
|
||||
# even though the value is documented as `str | None`. So we check for both.
|
||||
content_type = user_file.content_type
|
||||
if content_type is None or content_type == "":
|
||||
guessed_type = guess_type(uploaded_file_name)[0]
|
||||
if guessed_type is not None:
|
||||
@@ -92,6 +93,13 @@ def get_file_info(user_file: UploadedFile) -> tuple[str, str]:
|
||||
# different content-type from the filename.
|
||||
content_type = "application/octet-stream"
|
||||
|
||||
fake_msg = EmailMessage()
|
||||
extras = {}
|
||||
if user_file.content_type_extra:
|
||||
extras = {k: v.decode() if v else None for k, v in user_file.content_type_extra.items()} # type: ignore[attr-defined] # https://github.com/typeddjango/django-stubs/pull/2754
|
||||
fake_msg.add_header("content-type", content_type, **extras)
|
||||
content_type = fake_msg["content-type"]
|
||||
|
||||
uploaded_file_name = unquote(uploaded_file_name)
|
||||
|
||||
return uploaded_file_name, content_type
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
from datetime import timedelta
|
||||
from io import StringIO
|
||||
from unittest import mock
|
||||
@@ -215,6 +216,26 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
|
||||
self.assertEqual(result["Content-Type"], "image/png")
|
||||
consume_response(result)
|
||||
|
||||
def test_content_type_charset_specified(self) -> None:
|
||||
with tempfile.NamedTemporaryFile() as uploaded_file:
|
||||
uploaded_file.write("नाम में क्या रक्खा हे".encode())
|
||||
uploaded_file.seek(0)
|
||||
uploaded_file.content_type = "text/plain; test-key=test_value; charset=big5" # type: ignore[attr-defined]
|
||||
|
||||
result = self.api_post(
|
||||
self.example_user("hamlet"), "/api/v1/user_uploads", {"file": uploaded_file}
|
||||
)
|
||||
|
||||
self.login("hamlet")
|
||||
response_dict = self.assert_json_success(result)
|
||||
url = response_dict["url"]
|
||||
result = self.client_get(url)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
self.assertEqual(
|
||||
result["Content-Type"], 'text/plain; test-key="test_value"; charset="big5"'
|
||||
)
|
||||
consume_response(result)
|
||||
|
||||
# This test will go through the code path for uploading files onto LOCAL storage
|
||||
# when Zulip is in DEVELOPMENT mode.
|
||||
def test_file_upload_authed(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user