Skip to content

Commit

Permalink
Merge pull request #10 from rpatterson/master
Browse files Browse the repository at this point in the history
Recursively cleanup multipart payloads for encoding.
  • Loading branch information
mcdonc committed May 3, 2012
2 parents 4821855 + ef2d6d5 commit 83dfc5f
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 5 deletions.
2 changes: 2 additions & 0 deletions repoze/sendmail/delivery.py
Expand Up @@ -26,6 +26,7 @@
from zope.interface import implementer from zope.interface import implementer
from repoze.sendmail.interfaces import IMailDelivery from repoze.sendmail.interfaces import IMailDelivery
from repoze.sendmail.maildir import Maildir from repoze.sendmail.maildir import Maildir
from repoze.sendmail import encoding
from transaction.interfaces import IDataManager from transaction.interfaces import IDataManager
import transaction import transaction


Expand Down Expand Up @@ -79,6 +80,7 @@ class AbstractMailDelivery(object):
def send(self, fromaddr, toaddrs, message): def send(self, fromaddr, toaddrs, message):
assert isinstance(message, Message), \ assert isinstance(message, Message), \
'Message must be instance of email.message.Message' 'Message must be instance of email.message.Message'
encoding.cleanup_message(message)
messageid = message['Message-Id'] messageid = message['Message-Id']
if messageid is None: if messageid is None:
messageid = message['Message-Id'] = make_msgid('repoze.sendmail') messageid = message['Message-Id'] = make_msgid('repoze.sendmail')
Expand Down
31 changes: 28 additions & 3 deletions repoze/sendmail/encoding.py
Expand Up @@ -21,10 +21,10 @@
'content-disposition') 'content-disposition')




def encode_message(message, def cleanup_message(message,
addr_headers=ADDR_HEADERS, param_headers=PARAM_HEADERS): addr_headers=ADDR_HEADERS, param_headers=PARAM_HEADERS):
""" """
Encode a `Message` handling headers and payloads. Cleanup a `Message` handling header and payload charsets.
Headers are handled in the most sane way possible. Address names Headers are handled in the most sane way possible. Address names
are left in `ascii` if possible or encoded to `latin_1` or `utf-8` are left in `ascii` if possible or encoded to `latin_1` or `utf-8`
Expand All @@ -36,7 +36,8 @@ def encode_message(message,
encoding. Finally, all other header are left in `ascii` if encoding. Finally, all other header are left in `ascii` if
possible or encoded to `latin_1` or `utf-8` as a whole. possible or encoded to `latin_1` or `utf-8` as a whole.
The return is a byte string of the whole message. The message is modified in place and is also returned in such a
state that it can be safely encoded to ascii.
""" """
for key, value in message.items(): for key, value in message.items():
if key.lower() in addr_headers: if key.lower() in addr_headers:
Expand Down Expand Up @@ -72,7 +73,31 @@ def encode_message(message,
if PY_2: if PY_2:
payload = encoded payload = encoded
message.set_payload(payload, charset=best) message.set_payload(payload, charset=best)
elif isinstance(payload, list):
for part in payload:
cleanup_message(part)

return message


def encode_message(message,
addr_headers=ADDR_HEADERS, param_headers=PARAM_HEADERS):
"""
Encode a `Message` handling headers and payloads.
Headers are handled in the most sane way possible. Address names
are left in `ascii` if possible or encoded to `latin_1` or `utf-8`
and finally encoded according to RFC 2047 without encoding the
address, something the `email` stdlib package doesn't do.
Parameterized headers such as `filename` in the
`Content-Disposition` header, have their values encoded properly
while leaving the rest of the header to be handled without
encoding. Finally, all other header are left in `ascii` if
possible or encoded to `latin_1` or `utf-8` as a whole.
The return is a byte string of the whole message.
"""
cleanup_message(message)
return message.as_string().encode('ascii') return message.as_string().encode('ascii')




Expand Down
6 changes: 4 additions & 2 deletions repoze/sendmail/tests/test_delivery.py
Expand Up @@ -187,7 +187,7 @@ def tearDown(self):


def testNonASCIIAddrs(self): def testNonASCIIAddrs(self):
import os import os
from email.message import Message from email.mime import base
import transaction import transaction
from repoze.sendmail.delivery import QueuedMailDelivery from repoze.sendmail.delivery import QueuedMailDelivery
from repoze.sendmail._compat import b from repoze.sendmail._compat import b
Expand All @@ -197,7 +197,9 @@ def testNonASCIIAddrs(self):
non_ascii = b('LaPe\xc3\xb1a').decode('utf-8') non_ascii = b('LaPe\xc3\xb1a').decode('utf-8')
fromaddr = non_ascii+' <jim@example.com>' fromaddr = non_ascii+' <jim@example.com>'
toaddrs = (non_ascii+' <guido@recip.com>',) toaddrs = (non_ascii+' <guido@recip.com>',)
message = Message() message = base.MIMEBase('text', 'plain')
message['From'] = fromaddr
message['To'] = ','.join(toaddrs)


delivery.send(fromaddr, toaddrs, message) delivery.send(fromaddr, toaddrs, message)
self.assertTrue(os.listdir(os.path.join(self.maildir_path, 'tmp'))) self.assertTrue(os.listdir(os.path.join(self.maildir_path, 'tmp')))
Expand Down
26 changes: 26 additions & 0 deletions repoze/sendmail/tests/test_encoding.py
Expand Up @@ -207,3 +207,29 @@ def test_binary_body(self):
encoded = self._callFUT(message) encoded = self._callFUT(message)


self.assertTrue(encodestring(body) in encoded) self.assertTrue(encodestring(body) in encoded)

def test_encoding_multipart(self):
from email.mime import multipart
from email.mime import nonmultipart
from repoze.sendmail._compat import encodestring
from repoze.sendmail._compat import b

message = multipart.MIMEMultipart('alternative')

utf_8_encoded = b('mo \xe2\x82\xac')
utf_8 = utf_8_encoded.decode('utf_8')

plain_string = utf_8
plain_part = nonmultipart.MIMENonMultipart('plain', 'plain')
plain_part.set_payload(plain_string)
message.attach(plain_part)

html_string = '<p>'+utf_8+'</p>'
html_part = nonmultipart.MIMENonMultipart('text', 'html')
html_part.set_payload(html_string)
message.attach(html_part)

encoded = self._callFUT(message)

self.assertTrue(encodestring(plain_string.encode('utf_8')) in encoded)
self.assertTrue(encodestring(html_string.encode('utf_8')) in encoded)

0 comments on commit 83dfc5f

Please sign in to comment.