Skip to content

Commit

Permalink
Merge branch 'master' into jens_genericsetup_extra
Browse files Browse the repository at this point in the history
  • Loading branch information
dataflake committed Sep 14, 2017
2 parents 95c9339 + 633bd47 commit bf01c13
Show file tree
Hide file tree
Showing 10 changed files with 97 additions and 30 deletions.
6 changes: 6 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[run]
branch = True
source = Products.MailHost

[report]
precision = 2
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ develop-eggs/
eggs/
*.egg-info/
.tox/
.coverage
htmlcov/
coverage.xml
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ language: python
sudo: false
python:
- 2.7
- 3.4
- 3.5
- 3.6
install:
- pip install -U setuptools==33.1.1
- python bootstrap.py
Expand Down
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Changelog
- Move GenericSetup export/import support from the GenericSetup package
to MailHost as a setuptools extra.

- Python 3 compatibility

- add test coverage reporting

- Use `@implementer` class decorator.

- Drop long-deprecated support for uuencoded emails.
Expand Down
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
include *.txt
include *.rst
include .coveragerc
include tox.ini

recursive-include src *

Expand Down
8 changes: 7 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,18 @@
"License :: OSI Approved :: Zope Public License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2 :: Only",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Communications :: Email",
],
install_requires=[
'setuptools',
'six',
'AccessControl',
'Acquisition',
'DateTime',
Expand Down
49 changes: 29 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 All @@ -67,6 +64,7 @@
class MailHostError(Exception):
pass


manage_addMailHostForm = DTMLFile('dtml/addMailHost_form', globals())


Expand All @@ -86,6 +84,7 @@ def manage_addMailHost(self,
if REQUEST is not None:
REQUEST['RESPONSE'].redirect(self.absolute_url() + '/manage_main')


add = manage_addMailHost


Expand Down Expand Up @@ -326,12 +325,14 @@ def _send(self, mfrom, mto, messageText, immediate=False):

delivery.send(mfrom, mto, messageText)


InitializeClass(MailBase)


class MailHost(Persistent, MailBase):
"""persistent version"""


# All encodings supported by mimetools for BBB
ENCODERS = {
'base64': encoders.encode_base64,
Expand Down Expand Up @@ -362,19 +363,26 @@ def _encode(body, encode=None):
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 +411,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 +496,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
1 change: 1 addition & 0 deletions src/Products/MailHost/SendMailTag.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,5 @@ def render(self, md):

__call__ = render


String.commands['sendmail'] = SendMailTag
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
32 changes: 32 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,46 @@ envlist =
py34,
py35,
py36,
flake8,
coverage,

[testenv]
commands =
{envbindir}/buildout -c {toxinidir}/buildout.cfg buildout:directory={envdir} buildout:develop={toxinidir} bootstrap
{envbindir}/buildout -c {toxinidir}/buildout.cfg buildout:directory={envdir} buildout:develop={toxinidir} install test test_with_gs
{envbindir}/test
{envbindir}/test_with_gs

skip_install = true
deps =
setuptools==33.1.1
six
zc.buildout
coverage

setenv =
COVERAGE_FILE=.coverage.{envname}

[testenv:coverage]
basepython = python2.7
deps = coverage
setenv =
COVERAGE_FILE=.coverage
commands =
coverage erase
coverage combine
coverage html
coverage xml
coverage report

[testenv:flake8]
basepython = python2.7
deps =
flake8
flake8-html
flake8-debugger

commands =
- flake8 --format=html --htmldir={toxinidir}/parts/flake8 --doctests src tests setup.py {posargs}
flake8 --doctests src tests setup.py {posargs}

0 comments on commit bf01c13

Please sign in to comment.