diff --git a/django/core/mail/message.py b/django/core/mail/message.py index 421e353bfa..51af560b12 100644 --- a/django/core/mail/message.py +++ b/django/core/mail/message.py @@ -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) diff --git a/tests/mail/tests.py b/tests/mail/tests.py index 301e589409..b0fef71e98 100644 --- a/tests/mail/tests.py +++ b/tests/mail/tests.py @@ -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): """