Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Z
Zope
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
Zope
Commits
57785248
Commit
57785248
authored
Aug 14, 2009
by
Alec Mitchell
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Modifications to MailHost send method to fix bugs, specify charset encodings, and permit unicode.
parent
7fbe3b4f
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
498 additions
and
65 deletions
+498
-65
doc/CHANGES.rst
doc/CHANGES.rst
+11
-0
src/Products/MailHost/MailHost.py
src/Products/MailHost/MailHost.py
+151
-46
src/Products/MailHost/README.txt
src/Products/MailHost/README.txt
+7
-1
src/Products/MailHost/interfaces.py
src/Products/MailHost/interfaces.py
+2
-1
src/Products/MailHost/tests/testMailHost.py
src/Products/MailHost/tests/testMailHost.py
+327
-17
No files found.
doc/CHANGES.rst
View file @
57785248
...
@@ -37,6 +37,11 @@ Restructuring
...
@@ -37,6 +37,11 @@ Restructuring
Features Added
Features Added
++++++++++++++
++++++++++++++
- The send method of MailHost now supports unicode messages and
email.Message.Message objects. It also now accepts charset and
msg_type parameters to help with character, header and body
encoding.
- Updated packages:
- Updated packages:
- zope.app.appsetup = 3.12.0
- zope.app.appsetup = 3.12.0
...
@@ -56,6 +61,12 @@ Features Added
...
@@ -56,6 +61,12 @@ Features Added
Bugs Fixed
Bugs Fixed
++++++++++
++++++++++
- Fixed issue with sending text containing ':' from MailHost.
- MailHost will now ensure the headers it sets are 7bit.
- MailHost no longer generates garbage when given unicode input.
- Made C extensions work for 64-bit Python 2.5.x / 2.6.x.
- Made C extensions work for 64-bit Python 2.5.x / 2.6.x.
- Unfutzed test failures due to use of naive timezones with ``datetime``
- Unfutzed test failures due to use of naive timezones with ``datetime``
...
...
src/Products/MailHost/MailHost.py
View file @
57785248
...
@@ -14,10 +14,25 @@
...
@@ -14,10 +14,25 @@
$Id$
$Id$
"""
"""
from
cStringIO
import
StringIO
import
logging
import
logging
import
mimetools
import
re
import
rfc822
from
cStringIO
import
StringIO
from
copy
import
deepcopy
from
email.Header
import
Header
from
email.Charset
import
Charset
from
email
import
message_from_string
from
email.Message
import
Message
from
email
import
Encoders
try
:
import
email.utils
as
emailutils
except
ImportError
:
import
email.Utils
as
emailutils
import
email.Charset
# We import from a private module here because the email module
# doesn't provide a good public address list parser
from
email._parseaddr
import
AddressList
as
_AddressList
import
uu
from
threading
import
Lock
from
threading
import
Lock
import
time
import
time
...
@@ -49,6 +64,12 @@ queue_threads = {} # maps MailHost path -> queue processor threada
...
@@ -49,6 +64,12 @@ queue_threads = {} # maps MailHost path -> queue processor threada
LOG
=
logging
.
getLogger
(
'MailHost'
)
LOG
=
logging
.
getLogger
(
'MailHost'
)
# Encode utf-8 emails as Quoted Printable by default
email
.
Charset
.
add_charset
(
"utf-8"
,
email
.
Charset
.
QP
,
email
.
Charset
.
QP
,
"utf-8"
)
formataddr
=
emailutils
.
formataddr
parseaddr
=
emailutils
.
parseaddr
CHARSET_RE
=
re
.
compile
(
'charset=[
\
'
"]?([
\
w-]+)[
\
'"]?', re.IGNORECASE)
class MailHostError(Exception):
class MailHostError(Exception):
pass
pass
...
@@ -92,7 +113,6 @@ class MailBase(Implicit, Item, RoleManager):
...
@@ -92,7 +113,6 @@ class MailBase(Implicit, Item, RoleManager):
# timeout = 1.0 # unused?
# timeout = 1.0 # unused?
manage_options = (
manage_options = (
(
(
{'icon':'', 'label':'Edit',
{'icon':'', 'label':'Edit',
...
@@ -185,18 +205,19 @@ class MailBase(Implicit, Item, RoleManager):
...
@@ -185,18 +205,19 @@ class MailBase(Implicit, Item, RoleManager):
encode=None,
encode=None,
REQUEST=None,
REQUEST=None,
immediate=False,
immediate=False,
charset=None,
msg_type=None,
):
):
"""Render a mail template, then send it...
"""Render a mail template, then send it...
"""
"""
mtemplate = getattr(self, messageTemplate)
mtemplate = getattr(self, messageTemplate)
messageText = mtemplate(self, trueself.REQUEST)
messageText = mtemplate(self, trueself.REQUEST)
messageText
,
mto
,
mfrom
=
_mungeHeaders
(
messageText
,
mto
,
mfrom
)
trueself.send(messageText, mto=mto, mfrom=mfrom,
messageText
=
_encode
(
messageText
,
encode
)
encode=encode, immediate=immediate,
trueself
.
_send
(
mfrom
,
mto
,
messageText
,
immediat
e
)
charset=charset, msg_type=msg_typ
e)
if not statusTemplate:
if not statusTemplate:
return "
SEND
OK
"
return "
SEND
OK
"
try:
try:
stemplate = getattr(self, statusTemplate)
stemplate = getattr(self, statusTemplate)
return stemplate(self, trueself.REQUEST)
return stemplate(self, trueself.REQUEST)
...
@@ -211,10 +232,15 @@ class MailBase(Implicit, Item, RoleManager):
...
@@ -211,10 +232,15 @@ class MailBase(Implicit, Item, RoleManager):
subject=None,
subject=None,
encode=None,
encode=None,
immediate=False,
immediate=False,
charset=None,
msg_type=None,
):
):
messageText, mto, mfrom = _mungeHeaders(messageText, mto, mfrom,
messageText
,
mto
,
mfrom
=
_mungeHeaders
(
messageText
,
subject, charset, msg_type)
mto
,
mfrom
,
subject
)
# This encode step is mainly for BBB, encoding should be
# automatic if charset is passed. The automated charset-based
# encoding will be preferred if both encode and charset are
# provided.
messageText = _encode(messageText, encode)
messageText = _encode(messageText, encode)
self._send(mfrom, mto, messageText, immediate)
self._send(mfrom, mto, messageText, immediate)
...
@@ -327,68 +353,147 @@ InitializeClass(MailBase)
...
@@ -327,68 +353,147 @@ InitializeClass(MailBase)
class MailHost(Persistent, MailBase):
class MailHost(Persistent, MailBase):
"""persistent version"""
"""persistent version"""
def uu_encoder(msg):
"""For BBB only, don't send uuencoded emails"""
orig = StringIO(msg.get_payload())
encdata = StringIO()
uu.encode(orig, encdata)
msg.set_payload(encdata.getvalue())
# All encodings supported by mimetools for BBB
ENCODERS = {
'base64': Encoders.encode_base64,
'quoted-printable': Encoders.encode_quopri,
'7bit': Encoders.encode_7or8bit,
'8bit': Encoders.encode_7or8bit,
'x-uuencode': uu_encoder,
'uuencode': uu_encoder,
'x-uue': uu_encoder,
'uue': uu_encoder,
}
def _encode(body, encode=None):
def _encode(body, encode=None):
"""Manually sets an encoding and encodes the message if not
already encoded."""
if encode is None:
if encode is None:
return body
return body
mfile
=
StringIO
(
body
)
mo = message_from_string(body)
mo
=
mimetools
.
Message
(
mfile
)
current_coding = mo['Content-Transfer-Encoding']
if
mo
.
getencoding
()
!=
'7bit'
:
if current_coding == encode:
# already encoded correctly, may have been automated
return body
if mo['Content-Transfer-Encoding'] not in ['7bit', None]:
raise MailHostError, 'Message already encoded'
raise MailHostError, 'Message already encoded'
newmfile
=
StringIO
()
if encode in ENCODERS:
newmfile
.
write
(
''
.
join
(
mo
.
headers
)
)
ENCODERS[encode](mo
)
newmfile
.
write
(
'Content-Transfer-Encoding: %s
\
n
'
%
encode
)
if not mo['Content-Transfer-Encoding']:
if
not
mo
.
has_key
(
'Mime-Version'
):
mo['Content-Transfer-Encoding'] = encode
newmfile
.
write
(
'Mime-Version: 1.0
\
n
'
)
if not mo['Mime-Version']:
newmfile
.
write
(
'
\
n
'
)
mo['Mime-Version'] = '1.0'
mimetools
.
encode
(
mfile
,
newmfile
,
encode
)
return mo.as_string(
)
return
newmfile
.
getvalue
()
def _mungeHeaders(messageText, mto=None, mfrom=None, subject=None,
def
_mungeHeaders
(
messageText
,
mto
=
None
,
mfrom
=
None
,
subject
=
None
):
charset=None, msg_type
=None):
"""Sets missing message headers, and deletes Bcc.
"""Sets missing message headers, and deletes Bcc.
returns fixed message, fixed mto and fixed mfrom"""
returns fixed message, fixed mto and fixed mfrom"""
mfile
=
StringIO
(
messageText
.
lstrip
())
# If we have been given unicode fields, attempt to encode them
mo
=
rfc822
.
Message
(
mfile
)
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)
if isinstance(messageText, Message):
# We already have a message, make a copy to operate on
mo = deepcopy(messageText)
else:
# Otherwise parse the input message
mo = message_from_string(messageText)
if msg_type and not mo.get('Content-Type'):
# we don't use get_content_type because that has a default
# value of 'text/plain'
mo.set_type(msg_type)
charset_match = CHARSET_RE.search(mo['Content-Type'] or '')
if charset and not charset_match:
# Don't change the charset if already set
# This encodes the payload automatically based on the default
# encoding for the charset
mo.set_charset(charset)
elif charset_match and not charset:
# If a charset parameter was provided use it for header encoding below,
# Otherwise, try to use the charset provided in the message.
charset = charset_match.groups()[0]
# Parameters given will *always* override headers in the messageText.
# Parameters given will *always* override headers in the messageText.
# This is so that you can't override or add to subscribers by adding
# This is so that you can't override or add to subscribers by adding
# them to # the message text.
# them to # the message text.
if subject:
if subject:
mo
[
'Subject'
]
=
subject
# remove any existing header otherwise we get two
elif
not
mo
.
getheader
(
'Subject'
):
del mo['Subject']
mo['Subject'] = Header(subject, charset)
elif not mo.get('Subject'):
mo['Subject'] = '[No Subject]'
mo['Subject'] = '[No Subject]'
if mto:
if mto:
if isinstance(mto, basestring):
if isinstance(mto, basestring):
mto
=
[
rfc822
.
dump_address_pair
(
addr
)
mto = [
formataddr(addr) for addr in _AddressList(mto).addresslist]
for
addr
in
rfc822
.
AddressList
(
mto
)
]
if not mo.get('To'):
if
not
mo
.
getheader
(
'To'
):
mo['To'] = ', '.join(str(_encode_address_string(e, charset))
mo
[
'To'
]
=
','
.
join
(
mto
)
for e in
mto)
else:
else:
# If we don't have recipients, extract them from the message
mto = []
mto = []
for header in ('To', 'Cc', 'Bcc'):
for header in ('To', 'Cc', 'Bcc'):
v
=
mo
.
getheader
(
header
)
v =
','.join(mo.get_all(header) or []
)
if v:
if v:
mto
+=
[
rfc822
.
dump_address_pair
(
addr
)
mto += [
formataddr(addr) for addr in
for
addr
in
rfc822
.
AddressList
(
v
)
]
_AddressList(v).addresslist
]
if not mto:
if not mto:
raise MailHostError, "
No
message
recipients
designated
"
raise MailHostError, "
No
message
recipients
designated
"
if mfrom:
if mfrom:
mo
[
'From'
]
=
mfrom
# XXX: do we really want to override an explicitly set From
# header in the messageText
del mo['From']
mo['From'] = _encode_address_string(mfrom, charset)
else:
else:
if
mo
.
get
header
(
'From'
)
is
None
:
if mo.get('From') is None:
raise MailHostError,"
Message
missing
SMTP
Header
'From'"
raise MailHostError,"
Message
missing
SMTP
Header
'From'"
mfrom = mo['From']
mfrom = mo['From']
if
mo
.
get
header
(
'Bcc'
):
if mo.get('Bcc'):
mo
.
__delitem__
(
'Bcc'
)
del mo['Bcc']
if
not
mo
.
get
header
(
'Date'
):
if not mo.get('Date'):
mo['Date'] = DateTime().rfc822()
mo['Date'] = DateTime().rfc822()
mo
.
rewindbody
()
return mo.as_string(), mto, mfrom
finalmessage
=
mo
finalmessage
=
mo
.
__str__
()
+
'
\
n
'
+
mfile
.
read
()
def _try_encode(text, charset):
mfile
.
close
()
"""Attempt to encode using the default charset if none is
return
finalmessage
,
mto
,
mfrom
provided. Should we permit encoding errors?"""
if charset:
return text.encode(charset)
else:
return text.encode()
def _encode_address_string(text, charset):
"""Split the email into parts and use header encoding on the name
part if needed. We do this because the actual addresses need to be
ASCII with no encoding for most SMTP servers, but the non-address
parts should be encoded appropriately."""
header = Header()
name, addr = parseaddr(text)
try:
name.decode('us-ascii')
except UnicodeDecodeError:
# Encoded strings need an extra space
# XXX: should we be this tolerant of encoding errors here?
charset = Charset(charset)
name = charset.header_encode(name)
header.append(formataddr((name, addr)))
return header
src/Products/MailHost/README.txt
View file @
57785248
...
@@ -4,8 +4,14 @@ MailHost
...
@@ -4,8 +4,14 @@ MailHost
The MailHost product provides support for sending email from
The MailHost product provides support for sending email from
within the Zope environment using MailHost objects.
within the Zope environment using MailHost objects.
An optional character set can be specified to automatically encode unicode
input, and perform appropriate RFC 2822 header and body encoding for
the specified character set. Full python email.Message.Message objects
may be sent.
Email can optionally be encoded using Base64, Quoted-Printable
Email can optionally be encoded using Base64, Quoted-Printable
or UUEncode encoding.
or UUEncode encoding (though automatic body encoding will be applied if a
character set is specified).
MailHost provides integration with the Zope transaction system and optional
MailHost provides integration with the Zope transaction system and optional
support for asynchronous mail delivery. Asynchronous mail delivery is
support for asynchronous mail delivery. Asynchronous mail delivery is
...
...
src/Products/MailHost/interfaces.py
View file @
57785248
...
@@ -20,6 +20,7 @@ from zope.interface import Interface
...
@@ -20,6 +20,7 @@ from zope.interface import Interface
class
IMailHost
(
Interface
):
class
IMailHost
(
Interface
):
def
send
(
messageText
,
mto
=
None
,
mfrom
=
None
,
subject
=
None
,
encode
=
None
):
def
send
(
messageText
,
mto
=
None
,
mfrom
=
None
,
subject
=
None
,
encode
=
None
,
charset
=
None
,
msg_type
=
None
):
"""Send mail.
"""Send mail.
"""
"""
src/Products/MailHost/tests/testMailHost.py
View file @
57785248
...
@@ -16,6 +16,7 @@ $Id$
...
@@ -16,6 +16,7 @@ $Id$
"""
"""
import
unittest
import
unittest
from
email
import
message_from_string
from
Products.MailHost.MailHost
import
MailHost
from
Products.MailHost.MailHost
import
MailHost
from
Products.MailHost.MailHost
import
MailHostError
,
_mungeHeaders
from
Products.MailHost.MailHost
import
MailHostError
,
_mungeHeaders
...
@@ -30,6 +31,15 @@ class DummyMailHost(MailHost):
...
@@ -30,6 +31,15 @@ class DummyMailHost(MailHost):
self
.
sent
=
messageText
self
.
sent
=
messageText
self
.
immediate
=
immediate
self
.
immediate
=
immediate
class
FakeContent
(
object
):
def
__init__
(
self
,
template_name
,
message
):
def
template
(
self
,
context
,
REQUEST
=
None
):
return
message
setattr
(
self
,
template_name
,
template
)
@
staticmethod
def
check_status
(
context
,
REQUEST
=
None
):
return
'Message Sent'
class
TestMailHost
(
unittest
.
TestCase
):
class
TestMailHost
(
unittest
.
TestCase
):
...
@@ -59,14 +69,14 @@ This is the message body."""
...
@@ -59,14 +69,14 @@ This is the message body."""
# Add duplicated info
# Add duplicated info
resmsg
,
resto
,
resfrom
=
_mungeHeaders
(
msg
,
'recipient@domain.com'
,
resmsg
,
resto
,
resfrom
=
_mungeHeaders
(
msg
,
'recipient@domain.com'
,
'sender@domain.com'
,
'This is the subject'
)
'sender@domain.com'
,
'This is the subject'
)
self
.
failUnless
(
resto
==
[
'recipient@domain.com'
])
self
.
failUnless
Equal
(
resto
,
[
'recipient@domain.com'
])
self
.
failUnless
(
resfrom
==
'sender@domain.com'
)
self
.
failUnless
Equal
(
resfrom
,
'sender@domain.com'
)
# Add extra info
# Add extra info
resmsg
,
resto
,
resfrom
=
_mungeHeaders
(
msg
,
'recipient2@domain.com'
,
resmsg
,
resto
,
resfrom
=
_mungeHeaders
(
msg
,
'recipient2@domain.com'
,
'sender2@domain.com'
,
'This is the real subject'
)
'sender2@domain.com'
,
'This is the real subject'
)
self
.
failUnless
(
resto
==
[
'recipient2@domain.com'
])
self
.
failUnless
Equal
(
resto
,
[
'recipient2@domain.com'
])
self
.
failUnless
(
resfrom
==
'sender2@domain.com'
)
self
.
failUnless
Equal
(
resfrom
,
'sender2@domain.com'
)
def
testMissingHeaders
(
self
):
def
testMissingHeaders
(
self
):
msg
=
"""X-Header: Dummy header
msg
=
"""X-Header: Dummy header
...
@@ -90,15 +100,15 @@ This is the message body."""
...
@@ -90,15 +100,15 @@ This is the message body."""
# Specify all
# Specify all
resmsg
,
resto
,
resfrom
=
_mungeHeaders
(
msg
,
'recipient2@domain.com'
,
resmsg
,
resto
,
resfrom
=
_mungeHeaders
(
msg
,
'recipient2@domain.com'
,
'sender2@domain.com'
,
'This is the real subject'
)
'sender2@domain.com'
,
'This is the real subject'
)
self
.
failUnless
(
resto
==
[
'recipient2@domain.com'
])
self
.
failUnless
Equal
(
resto
,
[
'recipient2@domain.com'
])
self
.
failUnless
(
resfrom
==
'sender2@domain.com'
)
self
.
failUnless
Equal
(
resfrom
,
'sender2@domain.com'
)
def
testBCCHeader
(
self
):
def
testBCCHeader
(
self
):
msg
=
"From: me@example.com
\
n
Bcc: many@example.com
\
n
\
n
Message text"
msg
=
"From: me@example.com
\
n
Bcc: many@example.com
\
n
\
n
Message text"
# Specify only the "Bcc" header. Useful for bulk emails.
# Specify only the "Bcc" header. Useful for bulk emails.
resmsg
,
resto
,
resfrom
=
_mungeHeaders
(
msg
)
resmsg
,
resto
,
resfrom
=
_mungeHeaders
(
msg
)
self
.
failUnless
(
resto
==
[
'many@example.com'
])
self
.
failUnless
Equal
(
resto
,
[
'many@example.com'
])
self
.
failUnless
(
resfrom
==
'me@example.com'
)
self
.
failUnless
Equal
(
resfrom
,
'me@example.com'
)
def
testAddressParser
(
self
):
def
testAddressParser
(
self
):
...
@@ -112,18 +122,18 @@ This is the message body."""
...
@@ -112,18 +122,18 @@ This is the message body."""
# Test Address-Parser for To & CC given in messageText
# Test Address-Parser for To & CC given in messageText
resmsg
,
resto
,
resfrom
=
_mungeHeaders
(
msg
)
resmsg
,
resto
,
resfrom
=
_mungeHeaders
(
msg
)
self
.
failUnless
(
resto
==
[
'"Name, Nick" <recipient@domain.com>'
,
self
.
failUnless
Equal
(
resto
,
[
'"Name, Nick" <recipient@domain.com>'
,
'
"Foo Bar"
<foo@domain.com>'
,
'
Foo Bar
<foo@domain.com>'
,
'"Web, Jack" <jack@web.com>'
])
'"Web, Jack" <jack@web.com>'
])
self
.
failUnless
(
resfrom
==
'sender@domain.com'
)
self
.
failUnless
Equal
(
resfrom
,
'sender@domain.com'
)
# Test Address-Parser for a given mto-string
# Test Address-Parser for a given mto-string
resmsg
,
resto
,
resfrom
=
_mungeHeaders
(
msg
,
mto
=
'"Public, Joe" <pjoe@domain.com>,
"Foo Bar"
<foo@domain.com>'
)
resmsg
,
resto
,
resfrom
=
_mungeHeaders
(
msg
,
mto
=
'"Public, Joe" <pjoe@domain.com>,
Foo Bar
<foo@domain.com>'
)
self
.
failUnless
(
resto
==
[
'"Public, Joe" <pjoe@domain.com>'
,
self
.
failUnless
Equal
(
resto
,
[
'"Public, Joe" <pjoe@domain.com>'
,
'
"Foo Bar"
<foo@domain.com>'
])
'
Foo Bar
<foo@domain.com>'
])
self
.
failUnless
(
resfrom
==
'sender@domain.com'
)
self
.
failUnless
Equal
(
resfrom
,
'sender@domain.com'
)
def
testSendMessageOnly
(
self
):
def
testSendMessageOnly
(
self
):
msg
=
"""
\
msg
=
"""
\
...
@@ -147,7 +157,7 @@ This is the message body."""
...
@@ -147,7 +157,7 @@ This is the message body."""
outmsg
=
"""
\
outmsg
=
"""
\
Date: Sun, 27 Aug 2006 17:00:00 +0200
Date: Sun, 27 Aug 2006 17:00:00 +0200
Subject: This is the subject
Subject: This is the subject
To: "Name, Nick" <recipient@domain.com>,
"Foo Bar"
<foo@domain.com>
To: "Name, Nick" <recipient@domain.com>,
Foo Bar
<foo@domain.com>
From: sender@domain.com
From: sender@domain.com
This is the message body."""
This is the message body."""
...
@@ -167,7 +177,7 @@ This is the message body."""
...
@@ -167,7 +177,7 @@ This is the message body."""
outmsg
=
"""
\
outmsg
=
"""
\
Date: Sun, 27 Aug 2006 17:00:00 +0200
Date: Sun, 27 Aug 2006 17:00:00 +0200
Subject: This is the subject
Subject: This is the subject
To: "Name, Nick" <recipient@domain.com>,
"Foo Bar"
<foo@domain.com>
To: "Name, Nick" <recipient@domain.com>,
Foo Bar
<foo@domain.com>
From: sender@domain.com
From: sender@domain.com
This is the message body."""
This is the message body."""
...
@@ -208,6 +218,306 @@ This is the message body."""
...
@@ -208,6 +218,306 @@ This is the message body."""
self
.
assertEqual
(
mailhost
.
sent
,
outmsg
)
self
.
assertEqual
(
mailhost
.
sent
,
outmsg
)
self
.
assertEqual
(
mailhost
.
immediate
,
True
)
self
.
assertEqual
(
mailhost
.
immediate
,
True
)
def
testSendBodyWithUrl
(
self
):
# The implementation of rfc822.Message reacts poorly to
# message bodies containing ':' characters as in a url
msg
=
"Here's a nice link: http://www.zope.org/"
mailhost
=
self
.
_makeOne
(
'MailHost'
)
mailhost
.
send
(
messageText
=
msg
,
mto
=
'"Name, Nick" <recipient@domain.com>, "Foo Bar" <foo@domain.com>'
,
mfrom
=
'sender@domain.com'
,
subject
=
'This is the subject'
)
out
=
message_from_string
(
mailhost
.
sent
)
self
.
failUnlessEqual
(
out
.
get_payload
(),
msg
)
self
.
failUnlessEqual
(
out
[
'To'
],
'"Name, Nick" <recipient@domain.com>, Foo Bar <foo@domain.com>'
)
self
.
failUnlessEqual
(
out
[
'From'
],
'sender@domain.com'
)
def
testSendEncodedBody
(
self
):
# If a charset is specified the correct headers for content
# encoding will be set if not already set. Additionally, if
# 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
\
xa9
xt."
mailhost
=
self
.
_makeOne
(
'MailHost'
)
mailhost
.
send
(
messageText
=
msg
,
mto
=
'"Name, Nick" <recipient@domain.com>, "Foo Bar" <foo@domain.com>'
,
mfrom
=
'sender@domain.com'
,
subject
=
'This is the subject'
,
charset
=
'utf-8'
)
out
=
message_from_string
(
mailhost
.
sent
)
self
.
failUnlessEqual
(
out
[
'To'
],
'"Name, Nick" <recipient@domain.com>, Foo Bar <foo@domain.com>'
)
self
.
failUnlessEqual
(
out
[
'From'
],
'sender@domain.com'
)
# utf-8 will default to Quoted Printable encoding
self
.
failUnlessEqual
(
out
[
'Content-Transfer-Encoding'
],
'quoted-printable'
)
self
.
failUnlessEqual
(
out
[
'Content-Type'
],
'text/plain; charset="utf-8"'
)
self
.
failUnlessEqual
(
out
.
get_payload
(),
"Here's some encoded t=C3=A9xt."
)
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
\
xa9
s <jose@example.com>"
mto
=
"Ferran Adri
\
xc3
\
xa0
<ferran@example.com>"
subject
=
"
\
xc2
\
xbf
Esferificaci
\
xc3
\
xb3
n?"
mailhost
=
self
.
_makeOne
(
'MailHost'
)
mailhost
.
send
(
messageText
=
'A message.'
,
mto
=
mto
,
mfrom
=
mfrom
,
subject
=
subject
,
charset
=
'utf-8'
)
out
=
message_from_string
(
mailhost
.
sent
)
self
.
failUnlessEqual
(
out
[
'To'
],
'=?utf-8?q?Ferran_Adri=C3=A0?= <ferran@example.com>'
)
self
.
failUnlessEqual
(
out
[
'From'
],
'=?utf-8?q?Jos=C3=A9_Andr=C3=A9s?= <jose@example.com>'
)
self
.
failUnlessEqual
(
out
[
'Subject'
],
'=?utf-8?q?=C2=BFEsferificaci=C3=B3n=3F?='
)
# utf-8 will default to Quoted Printable encoding
self
.
failUnlessEqual
(
out
[
'Content-Transfer-Encoding'
],
'quoted-printable'
)
self
.
failUnlessEqual
(
out
[
'Content-Type'
],
'text/plain; charset="utf-8"'
)
self
.
failUnlessEqual
(
out
.
get_payload
(),
"A message."
)
def
testAlreadyEncodedMessage
(
self
):
# If the message already specifies encodings, it is
# essentially not altered this is true even if charset or
# msg_type is specified
msg
=
"""
\
From: =?utf-8?q?Jos=C3=A9_Andr=C3=A9s?= <jose@example.com>
To: =?utf-8?q?Ferran_Adri=C3=A0?= <ferran@example.com>
Subject: =?utf-8?q?=C2=BFEsferificaci=C3=B3n=3F?=
Date: Sun, 27 Aug 2006 17:00:00 +0200
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: base64
MIME-Version: 1.0 (Generated by testMailHost.py)
wqFVbiB0cnVjbyA8c3Ryb25nPmZhbnTDoXN0aWNvPC9zdHJvbmc+IQ=3D=3D
"""
mailhost
=
self
.
_makeOne
(
'MailHost'
)
mailhost
.
send
(
messageText
=
msg
)
self
.
failUnlessEqual
(
mailhost
.
sent
,
msg
)
mailhost
.
send
(
messageText
=
msg
,
msg_type
=
'text/plain'
)
# The msg_type is ignored if already set
self
.
failUnlessEqual
(
mailhost
.
sent
,
msg
)
def
testAlreadyEncodedMessageWithCharset
(
self
):
# If the message already specifies encodings, it is
# essentially not altered this is true even if charset or
# msg_type is specified
msg
=
"""
\
From: =?utf-8?q?Jos=C3=A9_Andr=C3=A9s?= <jose@example.com>
To: =?utf-8?q?Ferran_Adri=C3=A0?= <ferran@example.com>
Subject: =?utf-8?q?=C2=BFEsferificaci=C3=B3n=3F?=
Date: Sun, 27 Aug 2006 17:00:00 +0200
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: base64
MIME-Version: 1.0 (Generated by testMailHost.py)
wqFVbiB0cnVjbyA8c3Ryb25nPmZhbnTDoXN0aWNvPC9zdHJvbmc+IQ=3D=3D
"""
mailhost
=
self
.
_makeOne
(
'MailHost'
)
# Pass a different charset, which will apply to any explicitly
# set headers
mailhost
.
send
(
messageText
=
msg
,
subject
=
'
\
xbf
Esferificaci
\
xf3
n?'
,
charset
=
'iso-8859-1'
,
msg_type
=
'text/plain'
)
# The charset for the body should remain the same, but any
# headers passed into the method will be encoded using the
# specified charset
out
=
message_from_string
(
mailhost
.
sent
)
self
.
failUnlessEqual
(
out
[
'Content-Type'
],
'text/html; charset="utf-8"'
)
self
.
failUnlessEqual
(
out
[
'Content-Transfer-Encoding'
],
'base64'
)
# Headers set by parameter will be set using charset parameter
self
.
failUnlessEqual
(
out
[
'Subject'
],
'=?iso-8859-1?q?=BFEsferificaci=F3n=3F?='
)
# original headers will be unaltered
self
.
failUnlessEqual
(
out
[
'From'
],
'=?utf-8?q?Jos=C3=A9_Andr=C3=A9s?= <jose@example.com>'
)
def
testUnicodeMessage
(
self
):
# unicode messages and headers are decoded using the given charset
msg
=
unicode
(
"Here's some unencoded <strong>t
\
xc3
\
xa9
xt</strong>."
,
'utf-8'
)
mfrom
=
unicode
(
'Ferran Adri
\
xc3
\
xa0
<ferran@example.com>'
,
'utf-8'
)
subject
=
unicode
(
'
\
xc2
\
xa1
Andr
\
xc3
\
xa9
s!'
,
'utf-8'
)
mailhost
=
self
.
_makeOne
(
'MailHost'
)
mailhost
.
send
(
messageText
=
msg
,
mto
=
'"Name, Nick" <recipient@domain.com>'
,
mfrom
=
mfrom
,
subject
=
subject
,
charset
=
'utf-8'
,
msg_type
=
'text/html'
)
out
=
message_from_string
(
mailhost
.
sent
)
self
.
failUnlessEqual
(
out
[
'To'
],
'"Name, Nick" <recipient@domain.com>'
)
self
.
failUnlessEqual
(
out
[
'From'
],
'=?utf-8?q?Ferran_Adri=C3=A0?= <ferran@example.com>'
)
self
.
failUnlessEqual
(
out
[
'Subject'
],
'=?utf-8?q?=C2=A1Andr=C3=A9s!?='
)
self
.
failUnlessEqual
(
out
[
'Content-Transfer-Encoding'
],
'quoted-printable'
)
self
.
failUnlessEqual
(
out
[
'Content-Type'
],
'text/html; charset="utf-8"'
)
self
.
failUnlessEqual
(
out
.
get_payload
(),
"Here's some unencoded <strong>t=C3=A9xt</strong>."
)
def
testUnicodeNoEncodingErrors
(
self
):
# Unicode messages and headers raise errors if no charset is passed to
# send
msg
=
unicode
(
"Here's some unencoded <strong>t
\
xc3
\
xa9
xt</strong>."
,
'utf-8'
)
subject
=
unicode
(
'
\
xc2
\
xa1
Andr
\
xc3
\
xa9
s!'
,
'utf-8'
)
mailhost
=
self
.
_makeOne
(
'MailHost'
)
self
.
assertRaises
(
UnicodeEncodeError
,
mailhost
.
send
,
msg
,
mto
=
'"Name, Nick" <recipient@domain.com>'
,
mfrom
=
'Foo Bar <foo@domain.com>'
,
subject
=
subject
)
def
testUnicodeDefaultEncoding
(
self
):
# However if we pass unicode that can be encoded to the
# default encoding (generally 'us-ascii'), no error is raised.
# We include a date in the messageText to make inspecting the
# results more convenient.
msg
=
u"""
\
Date: Sun, 27 Aug 2006 17:00:00 +0200
Here's some unencoded <strong>text</strong>."""
subject
=
u'Andres!'
mailhost
=
self
.
_makeOne
(
'MailHost'
)
mailhost
.
send
(
msg
,
mto
=
u'"Name, Nick" <recipient@domain.com>'
,
mfrom
=
u'Foo Bar <foo@domain.com>'
,
subject
=
subject
)
out
=
mailhost
.
sent
# Ensure the results are not unicode
self
.
failUnlessEqual
(
out
,
"""
\
Date: Sun, 27 Aug 2006 17:00:00 +0200
Subject: Andres!
To: "Name, Nick" <recipient@domain.com>
From: Foo Bar <foo@domain.com>
Here's some unencoded <strong>text</strong>."""
)
self
.
failUnlessEqual
(
type
(
out
),
str
)
def
testSendMessageObject
(
self
):
# send will accept an email.Message.Message object directly
msg
=
message_from_string
(
"""
\
From: =?utf-8?q?Jos=C3=A9_Andr=C3=A9s?= <jose@example.com>
To: =?utf-8?q?Ferran_Adri=C3=A0?= <ferran@example.com>
Subject: =?utf-8?q?=C2=BFEsferificaci=C3=B3n=3F?=
Date: Sun, 27 Aug 2006 17:00:00 +0200
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: base64
MIME-Version: 1.1
wqFVbiB0cnVjbyA8c3Ryb25nPmZhbnTDoXN0aWNvPC9zdHJvbmc+IQ=3D=3D
"""
)
mailhost
=
self
.
_makeOne
(
'MailHost'
)
mailhost
.
send
(
msg
)
out
=
message_from_string
(
mailhost
.
sent
)
self
.
failUnlessEqual
(
out
.
as_string
(),
msg
.
as_string
())
# we can even alter a from and subject headers without affecting the
# original object
mailhost
.
send
(
msg
,
mfrom
=
'Foo Bar <foo@domain.com>'
,
subject
=
'Changed!'
)
out
=
message_from_string
(
mailhost
.
sent
)
# We need to make sure we didn't mutate the message we were passed
self
.
failIfEqual
(
out
.
as_string
(),
msg
.
as_string
())
self
.
failUnlessEqual
(
out
[
'From'
],
'Foo Bar <foo@domain.com>'
)
self
.
failUnlessEqual
(
msg
[
'From'
],
'=?utf-8?q?Jos=C3=A9_Andr=C3=A9s?= <jose@example.com>'
)
# The subject is encoded with the body encoding since no
# explicit encoding was specified
self
.
failUnlessEqual
(
out
[
'Subject'
],
'=?utf-8?q?Changed!?='
)
self
.
failUnlessEqual
(
msg
[
'Subject'
],
'=?utf-8?q?=C2=BFEsferificaci=C3=B3n=3F?='
)
def
testExplicitUUEncoding
(
self
):
# We can request a payload encoding explicitly, though this
# should probably be considered deprecated functionality.
mailhost
=
self
.
_makeOne
(
'MailHost'
)
# uuencoding
mailhost
.
send
(
'Date: Sun, 27 Aug 2006 17:00:00 +0200
\
n
\
n
A Message'
,
mfrom
=
'sender@domain.com'
,
mto
=
'Foo Bar <foo@domain.com>'
,
encode
=
'uue'
)
out
=
message_from_string
(
mailhost
.
sent
)
self
.
failUnlessEqual
(
mailhost
.
sent
,
"""
\
Date: Sun, 27 Aug 2006 17:00:00 +0200
Subject: [No Subject]
To: Foo Bar <foo@domain.com>
From: sender@domain.com
Content-Transfer-Encoding: uue
Mime-Version: 1.0
begin 666 -
)02!-97-S86=E
end
"""
)
def
testExplicitBase64Encoding
(
self
):
mailhost
=
self
.
_makeOne
(
'MailHost'
)
mailhost
.
send
(
'Date: Sun, 27 Aug 2006 17:00:00 +0200
\
n
\
n
A Message'
,
mfrom
=
'sender@domain.com'
,
mto
=
'Foo Bar <foo@domain.com>'
,
encode
=
'base64'
)
out
=
message_from_string
(
mailhost
.
sent
)
self
.
failUnlessEqual
(
mailhost
.
sent
,
"""
\
Date: Sun, 27 Aug 2006 17:00:00 +0200
Subject: [No Subject]
To: Foo Bar <foo@domain.com>
From: sender@domain.com
Content-Transfer-Encoding: base64
Mime-Version: 1.0
QSBNZXNzYWdl"""
)
def
testExplicit7bitEncoding
(
self
):
mailhost
=
self
.
_makeOne
(
'MailHost'
)
mailhost
.
send
(
'Date: Sun, 27 Aug 2006 17:00:00 +0200
\
n
\
n
A Message'
,
mfrom
=
'sender@domain.com'
,
mto
=
'Foo Bar <foo@domain.com>'
,
encode
=
'7bit'
)
out
=
message_from_string
(
mailhost
.
sent
)
self
.
failUnlessEqual
(
mailhost
.
sent
,
"""
\
Date: Sun, 27 Aug 2006 17:00:00 +0200
Subject: [No Subject]
To: Foo Bar <foo@domain.com>
From: sender@domain.com
Content-Transfer-Encoding: 7bit
Mime-Version: 1.0
A Message"""
)
def
testExplicit8bitEncoding
(
self
):
mailhost
=
self
.
_makeOne
(
'MailHost'
)
# We pass an encoded string with unspecified charset, it should be
# encoded 8bit
mailhost
.
send
(
'Date: Sun, 27 Aug 2006 17:00:00 +0200
\
n
\
n
A M
\
xc3
\
xa9
ssage'
,
mfrom
=
'sender@domain.com'
,
mto
=
'Foo Bar <foo@domain.com>'
,
encode
=
'8bit'
)
out
=
message_from_string
(
mailhost
.
sent
)
self
.
failUnlessEqual
(
mailhost
.
sent
,
"""
\
Date: Sun, 27 Aug 2006 17:00:00 +0200
Subject: [No Subject]
To: Foo Bar <foo@domain.com>
From: sender@domain.com
Content-Transfer-Encoding: 8bit
Mime-Version: 1.0
A M
\
xc3
\
xa9
ssage"""
)
def
testSendTemplate
(
self
):
content
=
FakeContent
(
'my_template'
,
'A Message'
)
mailhost
=
self
.
_makeOne
(
'MailHost'
)
result
=
mailhost
.
sendTemplate
(
content
,
'my_template'
,
mto
=
'Foo Bar <foo@domain.com>'
,
mfrom
=
'sender@domain.com'
)
self
.
failUnlessEqual
(
result
,
'SEND OK'
)
result
=
mailhost
.
sendTemplate
(
content
,
'my_template'
,
mto
=
'Foo Bar <foo@domain.com>'
,
mfrom
=
'sender@domain.com'
,
statusTemplate
=
'wrong_name'
)
self
.
failUnlessEqual
(
result
,
'SEND OK'
)
result
=
mailhost
.
sendTemplate
(
content
,
'my_template'
,
mto
=
'Foo Bar <foo@domain.com>'
,
mfrom
=
'sender@domain.com'
,
statusTemplate
=
'check_status'
)
self
.
failUnlessEqual
(
result
,
'Message Sent'
)
def
test_suite
():
def
test_suite
():
suite
=
unittest
.
TestSuite
()
suite
=
unittest
.
TestSuite
()
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment