[py3] Fixed mail tests with Python 3

This commit is contained in:
Claude Paroz 2012-08-09 12:12:22 +02:00
parent 5f8da527ab
commit 751774c29f
2 changed files with 46 additions and 42 deletions

View File

@ -11,11 +11,10 @@ from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase from email.mime.base import MIMEBase
from email.header import Header from email.header import Header
from email.utils import formatdate, getaddresses, formataddr, parseaddr from email.utils import formatdate, getaddresses, formataddr, parseaddr
from io import BytesIO
from django.conf import settings from django.conf import settings
from django.core.mail.utils import DNS_NAME from django.core.mail.utils import DNS_NAME
from django.utils.encoding import smart_bytes, force_text from django.utils.encoding import force_text
from django.utils import six from django.utils import six
@ -83,34 +82,34 @@ def forbid_multi_line_headers(name, val, encoding):
if '\n' in val or '\r' in val: if '\n' in val or '\r' in val:
raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name)) raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
try: try:
val = val.encode('ascii') val.encode('ascii')
except UnicodeEncodeError: except UnicodeEncodeError:
if name.lower() in ADDRESS_HEADERS: if name.lower() in ADDRESS_HEADERS:
val = ', '.join(sanitize_address(addr, encoding) val = ', '.join(sanitize_address(addr, encoding)
for addr in getaddresses((val,))) for addr in getaddresses((val,)))
else: else:
val = str(Header(val, encoding)) val = Header(val, encoding).encode()
else: else:
if name.lower() == 'subject': if name.lower() == 'subject':
val = Header(val) val = Header(val).encode()
return smart_bytes(name), val return str(name), val
def sanitize_address(addr, encoding): def sanitize_address(addr, encoding):
if isinstance(addr, six.string_types): if isinstance(addr, six.string_types):
addr = parseaddr(force_text(addr)) addr = parseaddr(force_text(addr))
nm, addr = addr nm, addr = addr
nm = str(Header(nm, encoding)) nm = Header(nm, encoding).encode()
try: try:
addr = addr.encode('ascii') addr.encode('ascii')
except UnicodeEncodeError: # IDN except UnicodeEncodeError: # IDN
if '@' in addr: if '@' in addr:
localpart, domain = addr.split('@', 1) localpart, domain = addr.split('@', 1)
localpart = str(Header(localpart, encoding)) localpart = str(Header(localpart, encoding))
domain = domain.encode('idna') domain = domain.encode('idna').decode('ascii')
addr = '@'.join([localpart, domain]) addr = '@'.join([localpart, domain])
else: else:
addr = str(Header(addr, encoding)) addr = Header(addr, encoding).encode()
return formataddr((nm, addr)) return formataddr((nm, addr))
@ -132,7 +131,7 @@ class SafeMIMEText(MIMEText):
This overrides the default as_string() implementation to not mangle This overrides the default as_string() implementation to not mangle
lines that begin with 'From '. See bug #13433 for details. lines that begin with 'From '. See bug #13433 for details.
""" """
fp = BytesIO() fp = six.StringIO()
g = Generator(fp, mangle_from_ = False) g = Generator(fp, mangle_from_ = False)
g.flatten(self, unixfrom=unixfrom) g.flatten(self, unixfrom=unixfrom)
return fp.getvalue() return fp.getvalue()
@ -156,7 +155,7 @@ class SafeMIMEMultipart(MIMEMultipart):
This overrides the default as_string() implementation to not mangle This overrides the default as_string() implementation to not mangle
lines that begin with 'From '. See bug #13433 for details. lines that begin with 'From '. See bug #13433 for details.
""" """
fp = BytesIO() fp = six.StringIO()
g = Generator(fp, mangle_from_ = False) g = Generator(fp, mangle_from_ = False)
g.flatten(self, unixfrom=unixfrom) g.flatten(self, unixfrom=unixfrom)
return fp.getvalue() return fp.getvalue()
@ -210,8 +209,7 @@ class EmailMessage(object):
def message(self): def message(self):
encoding = self.encoding or settings.DEFAULT_CHARSET encoding = self.encoding or settings.DEFAULT_CHARSET
msg = SafeMIMEText(smart_bytes(self.body, encoding), msg = SafeMIMEText(self.body, self.content_subtype, encoding)
self.content_subtype, encoding)
msg = self._create_message(msg) msg = self._create_message(msg)
msg['Subject'] = self.subject msg['Subject'] = self.subject
msg['From'] = self.extra_headers.get('From', self.from_email) msg['From'] = self.extra_headers.get('From', self.from_email)
@ -293,7 +291,7 @@ class EmailMessage(object):
basetype, subtype = mimetype.split('/', 1) basetype, subtype = mimetype.split('/', 1)
if basetype == 'text': if basetype == 'text':
encoding = self.encoding or settings.DEFAULT_CHARSET encoding = self.encoding or settings.DEFAULT_CHARSET
attachment = SafeMIMEText(smart_bytes(content, encoding), subtype, encoding) attachment = SafeMIMEText(content, subtype, encoding)
else: else:
# Encode non-text attachments with base64. # Encode non-text attachments with base64.
attachment = MIMEBase(basetype, subtype) attachment = MIMEBase(basetype, subtype)
@ -313,9 +311,11 @@ class EmailMessage(object):
attachment = self._create_mime_attachment(content, mimetype) attachment = self._create_mime_attachment(content, mimetype)
if filename: if filename:
try: try:
filename = filename.encode('ascii') filename.encode('ascii')
except UnicodeEncodeError: except UnicodeEncodeError:
filename = ('utf-8', '', filename.encode('utf-8')) if not six.PY3:
filename = filename.encode('utf-8')
filename = ('utf-8', '', filename)
attachment.add_header('Content-Disposition', 'attachment', attachment.add_header('Content-Disposition', 'attachment',
filename=filename) filename=filename)
return attachment return attachment

View File

@ -17,7 +17,7 @@ from django.core.mail.backends import console, dummy, locmem, filebased, smtp
from django.core.mail.message import BadHeaderError from django.core.mail.message import BadHeaderError
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils.six import StringIO from django.utils.six import PY3, StringIO
from django.utils.translation import ugettext_lazy from django.utils.translation import ugettext_lazy
@ -29,7 +29,7 @@ class MailTests(TestCase):
def test_ascii(self): def test_ascii(self):
email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com']) email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com'])
message = email.message() message = email.message()
self.assertEqual(message['Subject'].encode(), 'Subject') self.assertEqual(message['Subject'], 'Subject')
self.assertEqual(message.get_payload(), 'Content') self.assertEqual(message.get_payload(), 'Content')
self.assertEqual(message['From'], 'from@example.com') self.assertEqual(message['From'], 'from@example.com')
self.assertEqual(message['To'], 'to@example.com') self.assertEqual(message['To'], 'to@example.com')
@ -37,7 +37,7 @@ class MailTests(TestCase):
def test_multiple_recipients(self): def test_multiple_recipients(self):
email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com', 'other@example.com']) email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com', 'other@example.com'])
message = email.message() message = email.message()
self.assertEqual(message['Subject'].encode(), 'Subject') self.assertEqual(message['Subject'], 'Subject')
self.assertEqual(message.get_payload(), 'Content') self.assertEqual(message.get_payload(), 'Content')
self.assertEqual(message['From'], 'from@example.com') self.assertEqual(message['From'], 'from@example.com')
self.assertEqual(message['To'], 'to@example.com, other@example.com') self.assertEqual(message['To'], 'to@example.com, other@example.com')
@ -77,9 +77,10 @@ class MailTests(TestCase):
""" """
Test for space continuation character in long (ascii) subject headers (#7747) Test for space continuation character in long (ascii) subject headers (#7747)
""" """
email = EmailMessage('Long subject lines that get wrapped should use a space continuation character to get expected behavior in Outlook and Thunderbird', 'Content', 'from@example.com', ['to@example.com']) email = EmailMessage('Long subject lines that get wrapped should contain a space continuation character to get expected behavior in Outlook and Thunderbird', 'Content', 'from@example.com', ['to@example.com'])
message = email.message() message = email.message()
self.assertEqual(message['Subject'], 'Long subject lines that get wrapped should use a space continuation\n character to get expected behavior in Outlook and Thunderbird') # Note that in Python 3, maximum line length has increased from 76 to 78
self.assertEqual(message['Subject'].encode(), b'Long subject lines that get wrapped should contain a space continuation\n character to get expected behavior in Outlook and Thunderbird')
def test_message_header_overrides(self): def test_message_header_overrides(self):
""" """
@ -88,7 +89,7 @@ class MailTests(TestCase):
""" """
headers = {"date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"} 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) email = EmailMessage('subject', 'content', 'from@example.com', ['to@example.com'], headers=headers)
self.assertEqual(email.message().as_string(), b'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: Fri, 09 Nov 2001 01:08:47 -0000\nMessage-ID: foo\n\ncontent') self.assertEqual(email.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\ndate: Fri, 09 Nov 2001 01:08:47 -0000\nMessage-ID: foo\n\ncontent')
def test_from_header(self): def test_from_header(self):
""" """
@ -160,7 +161,7 @@ class MailTests(TestCase):
msg.attach_alternative(html_content, "text/html") msg.attach_alternative(html_content, "text/html")
msg.encoding = 'iso-8859-1' msg.encoding = 'iso-8859-1'
self.assertEqual(msg.message()['To'], '=?iso-8859-1?q?S=FCrname=2C_Firstname?= <to@example.com>') self.assertEqual(msg.message()['To'], '=?iso-8859-1?q?S=FCrname=2C_Firstname?= <to@example.com>')
self.assertEqual(msg.message()['Subject'].encode(), '=?iso-8859-1?q?Message_from_Firstname_S=FCrname?=') self.assertEqual(msg.message()['Subject'], '=?iso-8859-1?q?Message_from_Firstname_S=FCrname?=')
def test_encoding(self): def test_encoding(self):
""" """
@ -170,7 +171,7 @@ class MailTests(TestCase):
email = EmailMessage('Subject', 'Firstname Sürname is a great guy.', 'from@example.com', ['other@example.com']) email = EmailMessage('Subject', 'Firstname Sürname is a great guy.', 'from@example.com', ['other@example.com'])
email.encoding = 'iso-8859-1' email.encoding = 'iso-8859-1'
message = email.message() message = email.message()
self.assertTrue(message.as_string().startswith(b'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.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.assertEqual(message.get_payload(), 'Firstname S=FCrname is a great guy.') 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 # Make sure MIME attachments also works correctly with other encodings than utf-8
@ -179,8 +180,8 @@ class MailTests(TestCase):
msg = EmailMultiAlternatives('Subject', text_content, 'from@example.com', ['to@example.com']) msg = EmailMultiAlternatives('Subject', text_content, 'from@example.com', ['to@example.com'])
msg.encoding = 'iso-8859-1' msg.encoding = 'iso-8859-1'
msg.attach_alternative(html_content, "text/html") msg.attach_alternative(html_content, "text/html")
self.assertEqual(msg.message().get_payload(0).as_string(), b'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(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(), b'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>') 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>')
def test_attachments(self): def test_attachments(self):
"""Regression test for #9367""" """Regression test for #9367"""
@ -291,31 +292,31 @@ class MailTests(TestCase):
# Regression for #13433 - Make sure that EmailMessage doesn't mangle # Regression for #13433 - Make sure that EmailMessage doesn't mangle
# 'From ' in message body. # 'From ' in message body.
email = EmailMessage('Subject', 'From the future', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) email = EmailMessage('Subject', 'From the future', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
self.assertFalse(b'>From the future' in email.message().as_string()) self.assertFalse('>From the future' in email.message().as_string())
def test_dont_base64_encode(self): def test_dont_base64_encode(self):
# Ticket #3472 # Ticket #3472
# Shouldn't use Base64 encoding at all # Shouldn't use Base64 encoding at all
msg = EmailMessage('Subject', 'UTF-8 encoded body', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) msg = EmailMessage('Subject', 'UTF-8 encoded body', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
self.assertFalse(b'Content-Transfer-Encoding: base64' in msg.message().as_string()) self.assertFalse('Content-Transfer-Encoding: base64' in msg.message().as_string())
# Ticket #11212 # Ticket #11212
# Shouldn't use quoted printable, should detect it can represent content with 7 bit data # Shouldn't use quoted printable, should detect it can represent content with 7 bit data
msg = EmailMessage('Subject', 'Body with only ASCII characters.', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) msg = EmailMessage('Subject', 'Body with only ASCII characters.', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
s = msg.message().as_string() s = msg.message().as_string()
self.assertFalse(b'Content-Transfer-Encoding: quoted-printable' in s) self.assertFalse('Content-Transfer-Encoding: quoted-printable' in s)
self.assertTrue(b'Content-Transfer-Encoding: 7bit' in s) self.assertTrue('Content-Transfer-Encoding: 7bit' in s)
# Shouldn't use quoted printable, should detect it can represent content with 8 bit data # Shouldn't use quoted printable, should detect it can represent content with 8 bit data
msg = EmailMessage('Subject', 'Body with latin characters: àáä.', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) msg = EmailMessage('Subject', 'Body with latin characters: àáä.', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
s = msg.message().as_string() s = msg.message().as_string()
self.assertFalse(b'Content-Transfer-Encoding: quoted-printable' in s) self.assertFalse(str('Content-Transfer-Encoding: quoted-printable') in s)
self.assertTrue(b'Content-Transfer-Encoding: 8bit' in s) self.assertTrue(str('Content-Transfer-Encoding: 8bit') in s)
msg = EmailMessage('Subject', 'Body with non latin characters: А Б В Г Д Е Ж Ѕ З И І К Л М Н О П.', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) msg = EmailMessage('Subject', 'Body with non latin characters: А Б В Г Д Е Ж Ѕ З И І К Л М Н О П.', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
s = msg.message().as_string() s = msg.message().as_string()
self.assertFalse(b'Content-Transfer-Encoding: quoted-printable' in s) self.assertFalse(str('Content-Transfer-Encoding: quoted-printable') in s)
self.assertTrue(b'Content-Transfer-Encoding: 8bit' in s) self.assertTrue(str('Content-Transfer-Encoding: 8bit') in s)
class BaseEmailBackendTests(object): class BaseEmailBackendTests(object):
@ -440,7 +441,7 @@ class BaseEmailBackendTests(object):
email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com'], cc=['cc@example.com']) email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com'], cc=['cc@example.com'])
mail.get_connection().send_messages([email]) mail.get_connection().send_messages([email])
message = self.get_the_message() message = self.get_the_message()
self.assertStartsWith(message.as_string(), b'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.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: ')
def test_idn_send(self): def test_idn_send(self):
""" """
@ -519,9 +520,9 @@ class FileBackendTests(BaseEmailBackendTests, TestCase):
def get_mailbox_content(self): def get_mailbox_content(self):
messages = [] messages = []
for filename in os.listdir(self.tmp_dir): for filename in os.listdir(self.tmp_dir):
with open(os.path.join(self.tmp_dir, filename), 'rb') as fp: with open(os.path.join(self.tmp_dir, filename), 'r') as fp:
session = fp.read().split(b'\n' + (b'-' * 79) + b'\n') session = fp.read().split('\n' + ('-' * 79) + '\n')
messages.extend(email.message_from_string(m) for m in session if m) messages.extend(email.message_from_string(str(m)) for m in session if m)
return messages return messages
def test_file_sessions(self): def test_file_sessions(self):
@ -571,8 +572,8 @@ class ConsoleBackendTests(BaseEmailBackendTests, TestCase):
self.stream = sys.stdout = StringIO() self.stream = sys.stdout = StringIO()
def get_mailbox_content(self): def get_mailbox_content(self):
messages = self.stream.getvalue().split(b'\n' + (b'-' * 79) + b'\n') messages = self.stream.getvalue().split('\n' + ('-' * 79) + '\n')
return [email.message_from_string(m) for m in messages if m] return [email.message_from_string(str(m)) for m in messages if m]
def test_console_stream_kwarg(self): def test_console_stream_kwarg(self):
""" """
@ -600,7 +601,10 @@ class FakeSMTPServer(smtpd.SMTPServer, threading.Thread):
def process_message(self, peer, mailfrom, rcpttos, data): def process_message(self, peer, mailfrom, rcpttos, data):
m = email.message_from_string(data) m = email.message_from_string(data)
maddr = email.Utils.parseaddr(m.get('from'))[1] if PY3:
maddr = email.utils.parseaddr(m.get('from'))[1]
else:
maddr = email.Utils.parseaddr(m.get('from'))[1]
if mailfrom != maddr: if mailfrom != maddr:
return "553 '%s' != '%s'" % (mailfrom, maddr) return "553 '%s' != '%s'" % (mailfrom, maddr)
with self.sink_lock: with self.sink_lock: