mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #12422 -- Don't override global email charset behavior for utf-8.
Thanks simonb for the report, Claude Paroz and Susan Tan for their work on a fix.
This commit is contained in:
		| @@ -22,7 +22,8 @@ from django.utils import six | ||||
|  | ||||
| # Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from | ||||
| # some spam filters. | ||||
| Charset.add_charset('utf-8', Charset.SHORTEST, None, 'utf-8') | ||||
| utf8_charset = Charset.Charset('utf-8') | ||||
| utf8_charset.body_encoding = None  # Python defaults to BASE64 | ||||
|  | ||||
| # Default MIME type to use on attachments (if it is not explicitly given | ||||
| # and cannot be guessed). | ||||
| @@ -145,7 +146,16 @@ class SafeMIMEText(MIMEText): | ||||
|  | ||||
|     def __init__(self, text, subtype, charset): | ||||
|         self.encoding = charset | ||||
|         MIMEText.__init__(self, text, subtype, charset) | ||||
|         if charset == 'utf-8': | ||||
|             # Unfortunately, Python doesn't support setting a Charset instance | ||||
|             # as MIMEText init parameter (http://bugs.python.org/issue16324). | ||||
|             # We do it manually and trigger re-encoding of the payload. | ||||
|             MIMEText.__init__(self, text, subtype, None) | ||||
|             del self['Content-Transfer-Encoding'] | ||||
|             self.set_payload(text, utf8_charset) | ||||
|             self.replace_header('Content-Type', 'text/%s; charset="%s"' % (subtype, charset)) | ||||
|         else: | ||||
|             MIMEText.__init__(self, text, subtype, charset) | ||||
|  | ||||
|     def __setitem__(self, name, val): | ||||
|         name, val = forbid_multi_line_headers(name, val, self.encoding) | ||||
|   | ||||
| @@ -3,6 +3,7 @@ from __future__ import unicode_literals | ||||
|  | ||||
| import asyncore | ||||
| import email | ||||
| from email.mime.text import MIMEText | ||||
| import os | ||||
| import shutil | ||||
| import smtpd | ||||
| @@ -20,11 +21,32 @@ from django.core.mail.message import BadHeaderError | ||||
| from django.test import TestCase | ||||
| from django.test.utils import override_settings | ||||
| from django.utils.encoding import force_str, force_text | ||||
| from django.utils.six import PY3, StringIO | ||||
| from django.utils.six import PY3, StringIO, string_types | ||||
| from django.utils.translation import ugettext_lazy | ||||
|  | ||||
|  | ||||
| class MailTests(TestCase): | ||||
| class HeadersCheckMixin(object): | ||||
|  | ||||
|     def assertMessageHasHeaders(self, message, headers): | ||||
|         """ | ||||
|         Check that :param message: has all :param headers: headers. | ||||
|  | ||||
|         :param message: can be an instance of an email.Message subclass or a | ||||
|         string with the contens of an email message. | ||||
|         :param headers: should be a set of (header-name, header-value) tuples. | ||||
|         """ | ||||
|         if isinstance(message, string_types): | ||||
|             just_headers = message.split('\n\n', 1)[0] | ||||
|             hlist = just_headers.split('\n') | ||||
|             pairs = [hl.split(':', 1) for hl in hlist] | ||||
|             msg_headers = {(n, v.lstrip()) for (n, v) in pairs} | ||||
|         else: | ||||
|             msg_headers = set(message.items()) | ||||
|         self.assertTrue(headers.issubset(msg_headers), msg='Message is missing ' | ||||
|                         'the following headers: %s' % (headers - msg_headers),) | ||||
|  | ||||
|  | ||||
| class MailTests(HeadersCheckMixin, TestCase): | ||||
|     """ | ||||
|     Non-backend specific tests. | ||||
|     """ | ||||
| @@ -93,7 +115,7 @@ class MailTests(TestCase): | ||||
|         headers = {"date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"} | ||||
|         email = EmailMessage('subject', 'content', 'from@example.com', ['to@example.com'], headers=headers) | ||||
|  | ||||
|         self.assertEqual(sorted(email.message().items()), [ | ||||
|         self.assertMessageHasHeaders(email.message(), { | ||||
|             ('Content-Transfer-Encoding', '7bit'), | ||||
|             ('Content-Type', 'text/plain; charset="utf-8"'), | ||||
|             ('From', 'from@example.com'), | ||||
| @@ -102,7 +124,7 @@ class MailTests(TestCase): | ||||
|             ('Subject', 'subject'), | ||||
|             ('To', 'to@example.com'), | ||||
|             ('date', 'Fri, 09 Nov 2001 01:08:47 -0000'), | ||||
|         ]) | ||||
|         }) | ||||
|  | ||||
|     def test_from_header(self): | ||||
|         """ | ||||
| @@ -184,7 +206,13 @@ class MailTests(TestCase): | ||||
|         email = EmailMessage('Subject', 'Firstname Sürname is a great guy.', 'from@example.com', ['other@example.com']) | ||||
|         email.encoding = 'iso-8859-1' | ||||
|         message = email.message() | ||||
|         self.assertTrue(message.as_string().startswith('Content-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Subject\nFrom: from@example.com\nTo: other@example.com')) | ||||
|         self.assertMessageHasHeaders(message, { | ||||
|             ('MIME-Version', '1.0'), | ||||
|             ('Content-Type', 'text/plain; charset="iso-8859-1"'), | ||||
|             ('Content-Transfer-Encoding', 'quoted-printable'), | ||||
|             ('Subject', 'Subject'), | ||||
|             ('From', 'from@example.com'), | ||||
|             ('To', 'other@example.com')}) | ||||
|         self.assertEqual(message.get_payload(), 'Firstname S=FCrname is a great guy.') | ||||
|  | ||||
|         # Make sure MIME attachments also works correctly with other encodings than utf-8 | ||||
| @@ -193,8 +221,18 @@ class MailTests(TestCase): | ||||
|         msg = EmailMultiAlternatives('Subject', text_content, 'from@example.com', ['to@example.com']) | ||||
|         msg.encoding = 'iso-8859-1' | ||||
|         msg.attach_alternative(html_content, "text/html") | ||||
|         self.assertEqual(msg.message().get_payload(0).as_string(), 'Content-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\nFirstname S=FCrname is a great guy.') | ||||
|         self.assertEqual(msg.message().get_payload(1).as_string(), 'Content-Type: text/html; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\n<p>Firstname S=FCrname is a <strong>great</strong> guy.</p>') | ||||
|         payload0 = msg.message().get_payload(0) | ||||
|         self.assertMessageHasHeaders(payload0, { | ||||
|             ('MIME-Version', '1.0'), | ||||
|             ('Content-Type', 'text/plain; charset="iso-8859-1"'), | ||||
|             ('Content-Transfer-Encoding', 'quoted-printable')}) | ||||
|         self.assertTrue(payload0.as_string().endswith('\n\nFirstname S=FCrname is a great guy.')) | ||||
|         payload1 = msg.message().get_payload(1) | ||||
|         self.assertMessageHasHeaders(payload1, { | ||||
|             ('MIME-Version', '1.0'), | ||||
|             ('Content-Type', 'text/html; charset="iso-8859-1"'), | ||||
|             ('Content-Transfer-Encoding', 'quoted-printable')}) | ||||
|         self.assertTrue(payload1.as_string().endswith('\n\n<p>Firstname S=FCrname is a <strong>great</strong> guy.</p>')) | ||||
|  | ||||
|     def test_attachments(self): | ||||
|         """Regression test for #9367""" | ||||
| @@ -365,7 +403,31 @@ class MailTests(TestCase): | ||||
|         self.assertTrue(str('Child Subject') in parent_s) | ||||
|  | ||||
|  | ||||
| class BaseEmailBackendTests(object): | ||||
| class PythonGlobalState(TestCase): | ||||
|     """ | ||||
|     Tests for #12422 -- Django smarts (#2472/#11212) with charset of utf-8 text | ||||
|     parts shouldn't pollute global email Python package charset registry when | ||||
|     django.mail.message is imported. | ||||
|     """ | ||||
|  | ||||
|     def test_utf8(self): | ||||
|         txt = MIMEText('UTF-8 encoded body', 'plain', 'utf-8') | ||||
|         self.assertTrue('Content-Transfer-Encoding: base64' in txt.as_string()) | ||||
|  | ||||
|     def test_7bit(self): | ||||
|         txt = MIMEText('Body with only ASCII characters.', 'plain', 'utf-8') | ||||
|         self.assertTrue('Content-Transfer-Encoding: base64' in txt.as_string()) | ||||
|  | ||||
|     def test_8bit_latin(self): | ||||
|         txt = MIMEText('Body with latin characters: àáä.', 'plain', 'utf-8') | ||||
|         self.assertTrue(str('Content-Transfer-Encoding: base64') in txt.as_string()) | ||||
|  | ||||
|     def test_8bit_non_latin(self): | ||||
|         txt = MIMEText('Body with non latin characters: А Б В Г Д Е Ж Ѕ З И І К Л М Н О П.', 'plain', 'utf-8') | ||||
|         self.assertTrue(str('Content-Transfer-Encoding: base64') in txt.as_string()) | ||||
|  | ||||
|  | ||||
| class BaseEmailBackendTests(HeadersCheckMixin, object): | ||||
|     email_backend = None | ||||
|  | ||||
|     def setUp(self): | ||||
| @@ -523,7 +585,15 @@ class BaseEmailBackendTests(object): | ||||
|         email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com'], cc=['cc@example.com']) | ||||
|         mail.get_connection().send_messages([email]) | ||||
|         message = self.get_the_message() | ||||
|         self.assertStartsWith(message.as_string(), 'Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nCc: cc@example.com\nDate: ') | ||||
|         self.assertMessageHasHeaders(message, { | ||||
|             ('MIME-Version', '1.0'), | ||||
|             ('Content-Type', 'text/plain; charset="utf-8"'), | ||||
|             ('Content-Transfer-Encoding', '7bit'), | ||||
|             ('Subject', 'Subject'), | ||||
|             ('From', 'from@example.com'), | ||||
|             ('To', 'to@example.com'), | ||||
|             ('Cc', 'cc@example.com')}) | ||||
|         self.assertIn('\nDate: ', message.as_string()) | ||||
|  | ||||
|     def test_idn_send(self): | ||||
|         """ | ||||
| @@ -681,7 +751,14 @@ class ConsoleBackendTests(BaseEmailBackendTests, TestCase): | ||||
|         s = StringIO() | ||||
|         connection = mail.get_connection('django.core.mail.backends.console.EmailBackend', stream=s) | ||||
|         send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection) | ||||
|         self.assertTrue(s.getvalue().startswith('Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nDate: ')) | ||||
|         self.assertMessageHasHeaders(s.getvalue(), { | ||||
|             ('MIME-Version', '1.0'), | ||||
|             ('Content-Type', 'text/plain; charset="utf-8"'), | ||||
|             ('Content-Transfer-Encoding', '7bit'), | ||||
|             ('Subject', 'Subject'), | ||||
|             ('From', 'from@example.com'), | ||||
|             ('To', 'to@example.com')}) | ||||
|         self.assertIn('\nDate: ', s.getvalue()) | ||||
|  | ||||
|  | ||||
| class FakeSMTPChannel(smtpd.SMTPChannel): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user