1
0
mirror of https://github.com/django/django.git synced 2025-06-30 07:49:19 +00:00

Fixed #36478 -- Fixed inconsistent mail attachment handling.

Fixed an inconsistency between EmailMessage.attach() and .attachments
when attaching bytes content with a text/* mimetype. The attach()
function decodes UTF-8 bytes if possible and otherwise changes the
mimetype to application/octet-stream to preserve the content's unknown
encoding (refs #27007). Providing equivalent content directly in
EmailMessage.attachments did not apply the same logic, leading
to an "AttributeError: 'bytes' object has no attribute 'encode'"
in SafeMIMEText.set_payload().

Updated EmailMessage._create_mime_attachment() to match attach()'s
handling for text/* mimetypes with bytes content. Updated test cases
to accurately cover behavior on both paths.
This commit is contained in:
Mike Edmunds 2025-06-18 20:34:34 -07:00 committed by Sarah Boyce
parent 68a45d9a80
commit 23529b6627
2 changed files with 42 additions and 6 deletions

View File

@ -387,6 +387,15 @@ class EmailMessage:
email.Message or EmailMessage object, as well as a str.
"""
basetype, subtype = mimetype.split("/", 1)
if basetype == "text" and isinstance(content, bytes):
# This duplicates logic from EmailMessage.attach() to properly
# handle EmailMessage.attachments not created through attach().
try:
content = content.decode()
except UnicodeDecodeError:
mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
basetype, subtype = mimetype.split("/", 1)
if basetype == "text":
encoding = self.encoding or settings.DEFAULT_CHARSET
attachment = SafeMIMEText(content, subtype, encoding)

View File

@ -901,12 +901,39 @@ class MailTests(MailTestsMixin, SimpleTestCase):
self.assertEqual(actual.mimetype, expected_mimetype)
def test_attach_text_as_bytes(self):
msg = EmailMessage()
msg.attach("file.txt", b"file content\n")
filename, content, mimetype = self.get_decoded_attachments(msg)[0]
self.assertEqual(filename, "file.txt")
self.assertEqual(content, "file content\n")
self.assertEqual(mimetype, "text/plain")
"""
For text/* attachments, EmailMessage.attach() decodes bytes as UTF-8
if possible and changes to DEFAULT_ATTACHMENT_MIME_TYPE if not.
"""
email = EmailMessage()
# Mimetype guessing identifies these as text/plain from the .txt extensions.
email.attach("utf8.txt", "ütƒ-8\n".encode())
email.attach("not-utf8.txt", b"\x86unknown-encoding\n")
attachments = self.get_decoded_attachments(email)
self.assertEqual(attachments[0], ("utf8.txt", "ütƒ-8\n", "text/plain"))
self.assertEqual(
attachments[1],
("not-utf8.txt", b"\x86unknown-encoding\n", "application/octet-stream"),
)
def test_attach_text_as_bytes_using_property(self):
"""
The logic described in test_attach_text_as_bytes() also applies
when directly setting the EmailMessage.attachments property.
"""
email = EmailMessage()
email.attachments = [
("utf8.txt", "ütƒ-8\n".encode(), "text/plain"),
("not-utf8.txt", b"\x86unknown-encoding\n", "text/plain"),
]
attachments = self.get_decoded_attachments(email)
self.assertEqual(len(attachments), 2)
attachments = self.get_decoded_attachments(email)
self.assertEqual(attachments[0], ("utf8.txt", "ütƒ-8\n", "text/plain"))
self.assertEqual(
attachments[1],
("not-utf8.txt", b"\x86unknown-encoding\n", "application/octet-stream"),
)
def test_attach_utf8_text_as_bytes(self):
"""