Skip to content

Commit

Permalink
- Python 3 compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
dataflake committed Sep 14, 2017
1 parent 843da5c commit 9e14e6d
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 29 deletions.
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Changelog
4.0 (unreleased)
----------------

- Python 3 compatibility

- add test coverage reporting

- Use `@implementer` class decorator.

- Drop long-deprecated support for uuencoded emails.
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
],
install_requires=[
'setuptools',
'six',
'AccessControl',
'Acquisition',
'DateTime',
Expand Down
43 changes: 23 additions & 20 deletions src/Products/MailHost/MailHost.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import logging
from os.path import realpath
import re
import sys
import six
from threading import Lock
import time

Expand All @@ -51,9 +51,6 @@
from Products.MailHost.interfaces import IMailHost
from Products.MailHost.decorator import synchronized

if sys.version_info > (3, 0):
basestring = str
unicode = str

queue_threads = {} # maps MailHost path -> queue processor threada

Expand Down Expand Up @@ -361,20 +358,25 @@ def _encode(body, encode=None):
mo['Mime-Version'] = '1.0'
return mo.as_string()

def _string_transform(text, charset=None):
if six.PY2 and isinstance(text, six.text_type):
# Unicode under Python 2
return _try_encode(text, charset)

if six.PY3 and isinstance(text, bytes):
# Already-encoded byte strings which the email module does not like
return text.decode(charset)

return text

def _mungeHeaders(messageText, mto=None, mfrom=None, subject=None,
charset=None, msg_type=None):
"""Sets missing message headers, and deletes Bcc.
returns fixed message, fixed mto and fixed mfrom"""
# If we have been given unicode fields, attempt to encode them
if isinstance(messageText, unicode):
messageText = _try_encode(messageText, charset)
if isinstance(mto, unicode):
mto = _try_encode(mto, charset)
if isinstance(mfrom, unicode):
mfrom = _try_encode(mfrom, charset)
if isinstance(subject, unicode):
subject = _try_encode(subject, charset)
messageText = _string_transform(messageText, charset)
mto = _string_transform(mto, charset)
mfrom = _string_transform(mfrom, charset)
subject = _string_transform(subject, charset)

if isinstance(messageText, Message):
# We already have a message, make a copy to operate on
Expand Down Expand Up @@ -403,7 +405,7 @@ def _mungeHeaders(messageText, mto=None, mfrom=None, subject=None,
mo['Subject'] = '[No Subject]'

if mto:
if isinstance(mto, basestring):
if isinstance(mto, six.string_types):
mto = [formataddr(addr) for addr in getaddresses((mto, ))]
if not mo.get('To'):
mo['To'] = ', '.join(str(_encode_address_string(e, charset))
Expand Down Expand Up @@ -488,12 +490,13 @@ def _encode_address_string(text, charset):
parts should be encoded appropriately."""
header = Header()
name, addr = parseaddr(text)
try:
name.decode('us-ascii')
except UnicodeDecodeError:
if charset:
charset = Charset(charset)
name = charset.header_encode(name)
if isinstance(name, six.binary_type):
try:
name.decode('us-ascii')
except UnicodeDecodeError:
if charset:
charset = Charset(charset)
name = charset.header_encode(name)
# We again replace rather than raise an error or pass an 8bit string
header.append(formataddr((name, addr)), errors='replace')
return header
19 changes: 10 additions & 9 deletions src/Products/MailHost/tests/testMailHost.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,12 @@
"""

from email import message_from_string
import sys
import six
import unittest

from Products.MailHost.MailHost import MailHost
from Products.MailHost.MailHost import MailHostError, _mungeHeaders

if sys.version_info > (3, 0):
unicode = str


class DummyMailHost(MailHost):
meta_type = 'Dummy Mail Host'
Expand Down Expand Up @@ -273,7 +270,7 @@ def testSendEncodedBody(self):
# there is a default transfer encoding for the charset, then
# the content will be encoded and the transfer encoding header
# will be set.
msg = "Here's some encoded t\xc3\xa9xt."
msg = b"Here's some encoded t\xc3\xa9xt."
mailhost = self._makeOne('MailHost')
mailhost.send(messageText=msg,
mto='"Name, Nick" <recipient@domain.com>, '
Expand All @@ -297,9 +294,9 @@ def testSendEncodedBody(self):
def testEncodedHeaders(self):
# Headers are encoded automatically, email headers are encoded
# piece-wise to ensure the adresses remain ASCII
mfrom = "Jos\xc3\xa9 Andr\xc3\xa9s <jose@example.com>"
mto = "Ferran Adri\xc3\xa0 <ferran@example.com>"
subject = "\xc2\xbfEsferificaci\xc3\xb3n?"
mfrom = b"Jos\xc3\xa9 Andr\xc3\xa9s <jose@example.com>"
mto = b"Ferran Adri\xc3\xa0 <ferran@example.com>"
subject = b"\xc2\xbfEsferificaci\xc3\xb3n?"
mailhost = self._makeOne('MailHost')
mailhost.send(messageText='A message.', mto=mto, mfrom=mfrom,
subject=subject, charset='utf-8')
Expand Down Expand Up @@ -377,6 +374,7 @@ def testAlreadyEncodedMessageWithCharset(self):
out['From'],
'=?utf-8?q?Jos=C3=A9_Andr=C3=A9s?= <jose@example.com>')

@unittest.skipIf(six.PY3, 'Test not applicable on Python 3.')
def testUnicodeMessage(self):
# unicode messages and headers are decoded using the given charset
msg = unicode("Here's some unencoded <strong>t\xc3\xa9xt</strong>.",
Expand All @@ -402,6 +400,7 @@ def testUnicodeMessage(self):
out.get_payload(),
"Here's some unencoded <strong>t=C3=A9xt</strong>.")

@unittest.skipIf(six.PY3, 'Test not applicable on Python 3.')
def testUnicodeNoEncodingErrors(self):
# Unicode messages and headers raise errors if no charset is passed to
# send
Expand Down Expand Up @@ -480,7 +479,9 @@ def testExplicitBase64Encoding(self):
mailhost.send('Date: Sun, 27 Aug 2006 17:00:00 +0200\n\nA Message',
mfrom='sender@domain.com',
mto='Foo Bar <foo@domain.com>', encode='base64')
self.failUnlessEqual(mailhost.sent, """\
# Explicitly stripping the output here since the base64 encoder
# in Python 3 adds a line break at the end.
self.failUnlessEqual(mailhost.sent.strip(), """\
Date: Sun, 27 Aug 2006 17:00:00 +0200
Subject: [No Subject]
To: Foo Bar <foo@domain.com>
Expand Down

0 comments on commit 9e14e6d

Please sign in to comment.