Skip to content

Commit cbfad5f

Browse files
author
Richard Jones
committed
migrate from MimeWriter to email
1 parent bfb0b86 commit cbfad5f

File tree

7 files changed

+140
-168
lines changed

7 files changed

+140
-168
lines changed

COPYING.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Roundup Licensing
22
-----------------
33

4-
Copyright (c) 2003 Richard Jones ([email protected])
4+
Copyright (c) 2003-2009 Richard Jones ([email protected])
55
Copyright (c) 2002 eKit.com Inc (http://www.ekit.com/)
66
Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
77

README.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Roundup: an Issue-Tracking System for Knowledge Workers
33
=======================================================
44

5-
Copyright (c) 2003 Richard Jones ([email protected])
5+
Copyright (c) 2003-2009 Richard Jones ([email protected])
66
Copyright (c) 2002 eKit.com Inc (http://www.ekit.com/)
77
Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
88

@@ -31,7 +31,7 @@ directory.
3131
Upgrading
3232
=========
3333
For upgrading instructions, please see upgrading.txt in the "doc" directory.
34-
34+
3535

3636
Usage and Other Information
3737
===========================

roundup/anypy/TODO.txt

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,4 @@ Python compatiblity TODO
55
the subprocess module is available since Python 2.4,
66
thus a roundup.anypy.subprocess_ module is needed
77

8-
- the MimeWriter module is deprecated as of Python 2.6. The email package is
9-
available since Python 2.2, thus we should manage without a ...email_
10-
module; however, it has suffered some API changes over the time
11-
(http://docs.python.org/library/email.html#package-history),
12-
so this is not sure.
13-
14-
Here's an incomplete replacement table:
15-
16-
MimeWriter usage checked for
17-
-> email usage Python ...
18-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~
19-
MimeWriter.MimeWriter
20-
-> email.Message.Message (2.3)
21-
22-
MimeWriter.MimeWrite.addheader
23-
-> email.Message.Message.add_header (2.3)
24-
258
# vim: si

roundup/mailer.py

Lines changed: 63 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,28 @@
33
__docformat__ = 'restructuredtext'
44
# $Id: mailer.py,v 1.22 2008-07-21 01:44:58 richard Exp $
55

6-
import time, quopri, os, socket, smtplib, re, sys, traceback
6+
import time, quopri, os, socket, smtplib, re, sys, traceback, email
77

88
from cStringIO import StringIO
9-
from MimeWriter import MimeWriter
109

11-
from roundup.rfc2822 import encode_header
1210
from roundup import __version__
1311
from roundup.date import get_timezone
1412

15-
try:
16-
from email.Utils import formatdate
17-
except ImportError:
18-
def formatdate():
19-
return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime())
13+
from email.Utils import formatdate, formataddr
14+
from email.Message import Message
15+
from email.Header import Header
16+
from email.MIMEText import MIMEText
17+
from email.MIMEMultipart import MIMEMultipart
2018

2119
class MessageSendError(RuntimeError):
2220
pass
2321

22+
def encode_quopri(msg):
23+
orig = msg.get_payload()
24+
encdata = quopri.encodestring(orig)
25+
msg.set_payload(encdata)
26+
msg['Content-Transfer-Encoding'] = 'quoted-printable'
27+
2428
class Mailer:
2529
"""Roundup-specific mail sending."""
2630
def __init__(self, config):
@@ -41,7 +45,7 @@ def __init__(self, config):
4145
os.environ['TZ'] = get_timezone(self.config.TIMEZONE).tzname(None)
4246
time.tzset()
4347

44-
def get_standard_message(self, to, subject, author=None):
48+
def get_standard_message(self, to, subject, author=None, multipart=False):
4549
'''Form a standard email message from Roundup.
4650
4751
"to" - recipients list
@@ -55,38 +59,48 @@ def get_standard_message(self, to, subject, author=None):
5559
'''
5660
# encode header values if they need to be
5761
charset = getattr(self.config, 'EMAIL_CHARSET', 'utf-8')
58-
tracker_name = self.config.TRACKER_NAME
59-
if charset != 'utf-8':
60-
tracker = unicode(tracker_name, 'utf-8').encode(charset)
62+
tracker_name = unicode(self.config.TRACKER_NAME, 'utf-8')
6163
if not author:
62-
author = straddr((tracker_name, self.config.ADMIN_EMAIL))
64+
author = formataddr((tracker_name, self.config.ADMIN_EMAIL))
65+
else:
66+
name = unicode(author[0], 'utf-8')
67+
author = formataddr((name, author[1]))
68+
69+
if multipart:
70+
message = MIMEMultipart()
6371
else:
64-
name = author[0]
65-
if charset != 'utf-8':
66-
name = unicode(name, 'utf-8').encode(charset)
67-
author = straddr((encode_header(name, charset), author[1]))
68-
69-
message = StringIO()
70-
writer = MimeWriter(message)
71-
writer.addheader('Subject', encode_header(subject, charset))
72-
writer.addheader('To', ', '.join(to))
73-
writer.addheader('From', author)
74-
writer.addheader('Date', formatdate(localtime=True))
72+
message = Message()
73+
message.set_charset(charset)
74+
message['Content-Type'] = 'text/plain; charset="%s"'%charset
75+
76+
try:
77+
message['Subject'] = subject.encode('ascii')
78+
except UnicodeError:
79+
message['Subject'] = Header(subject, charset)
80+
message['To'] = ', '.join(to)
81+
try:
82+
message['From'] = author.encode('ascii')
83+
except UnicodeError:
84+
message['From'] = Header(author, charset)
85+
message['Date'] = formatdate(localtime=True)
7586

7687
# add a Precedence header so autoresponders ignore us
77-
writer.addheader('Precedence', 'bulk')
88+
message['Precedence'] = 'bulk'
7889

7990
# Add a unique Roundup header to help filtering
80-
writer.addheader('X-Roundup-Name', encode_header(tracker_name,
81-
charset))
91+
try:
92+
message['X-Roundup-Name'] = tracker_name.encode('ascii')
93+
except UnicodeError:
94+
message['X-Roundup-Name'] = Header(tracker_name, charset)
95+
8296
# and another one to avoid loops
83-
writer.addheader('X-Roundup-Loop', 'hello')
97+
message['X-Roundup-Loop'] = 'hello'
8498
# finally, an aid to debugging problems
85-
writer.addheader('X-Roundup-Version', __version__)
99+
message['X-Roundup-Version'] = __version__
86100

87-
writer.addheader('MIME-Version', '1.0')
101+
message['MIME-Version'] = '1.0'
88102

89-
return message, writer
103+
return message
90104

91105
def standard_message(self, to, subject, content, author=None):
92106
"""Send a standard message.
@@ -96,15 +110,12 @@ def standard_message(self, to, subject, content, author=None):
96110
- subject: the subject as a string.
97111
- content: the body of the message as a string.
98112
- author: the sender as a (name, address) tuple
99-
"""
100-
message, writer = self.get_standard_message(to, subject, author)
101-
102-
writer.addheader('Content-Transfer-Encoding', 'quoted-printable')
103-
body = writer.startbody('text/plain; charset=utf-8')
104-
content = StringIO(content)
105-
quopri.encode(content, body, 0)
106113
107-
self.smtp_send(to, message)
114+
All strings are assumed to be UTF-8 encoded.
115+
"""
116+
message = self.get_standard_message(to, subject, author)
117+
message.set_payload(content)
118+
self.smtp_send(to, str(message))
108119

109120
def bounce_message(self, bounced_message, to, error,
110121
subject='Failed issue tracker submission'):
@@ -128,35 +139,29 @@ def bounce_message(self, bounced_message, to, error,
128139
elif error_messages_to == "both":
129140
to.append(dispatcher_email)
130141

131-
message, writer = self.get_standard_message(to, subject)
142+
message = self.get_standard_message(to, subject)
132143

133-
part = writer.startmultipartbody('mixed')
134-
part = writer.nextpart()
135-
part.addheader('Content-Transfer-Encoding', 'quoted-printable')
136-
body = part.startbody('text/plain; charset=utf-8')
137-
body.write(quopri.encodestring ('\n'.join(error)))
144+
# add the error text
145+
part = MIMEText(error)
146+
message.attach(part)
138147

139148
# attach the original message to the returned message
140-
part = writer.nextpart()
141-
part.addheader('Content-Disposition', 'attachment')
142-
part.addheader('Content-Description', 'Message you sent')
143-
body = part.startbody('text/plain')
144-
145-
for header in bounced_message.headers:
146-
body.write(header)
147-
body.write('\n')
148149
try:
149150
bounced_message.rewindbody()
150151
except IOError, message:
151152
body.write("*** couldn't include message body: %s ***"
152153
% bounced_message)
153154
else:
154155
body.write(bounced_message.fp.read())
156+
part = MIMEText(bounced_message.fp.read())
157+
part['Content-Disposition'] = 'attachment'
158+
for header in bounced_message.headers:
159+
part.write(header)
160+
message.attach(part)
155161

156-
writer.lastpart()
157-
162+
# send
158163
try:
159-
self.smtp_send(to, message)
164+
self.smtp_send(to, str(message))
160165
except MessageSendError:
161166
# squash mail sending errors when bouncing mail
162167
# TODO this *could* be better, as we could notify admin of the
@@ -184,16 +189,14 @@ def smtp_send(self, to, message):
184189
# don't send - just write to a file
185190
open(self.debug, 'a').write('FROM: %s\nTO: %s\n%s\n' %
186191
(self.config.ADMIN_EMAIL,
187-
', '.join(to),
188-
message.getvalue()))
192+
', '.join(to), message))
189193
else:
190194
# now try to send the message
191195
try:
192196
# send the message as admin so bounces are sent there
193197
# instead of to roundup
194198
smtp = SMTPConnection(self.config)
195-
smtp.sendmail(self.config.ADMIN_EMAIL, to,
196-
message.getvalue())
199+
smtp.sendmail(self.config.ADMIN_EMAIL, to, message)
197200
except socket.error, value:
198201
raise MessageSendError("Error: couldn't send email: "
199202
"mailhost %s"%value)
@@ -217,20 +220,4 @@ def __init__(self, config):
217220
if mailuser:
218221
self.login(mailuser, config["MAIL_PASSWORD"])
219222

220-
# use the 'email' module, either imported, or our copied version
221-
try :
222-
from email.Utils import formataddr as straddr
223-
except ImportError :
224-
# code taken from the email package 2.4.3
225-
def straddr(pair, specialsre = re.compile(r'[][\()<>@,:;".]'),
226-
escapesre = re.compile(r'[][\()"]')):
227-
name, address = pair
228-
if name:
229-
quotes = ''
230-
if specialsre.search(name):
231-
quotes = '"'
232-
name = escapesre.sub(r'\\\g<0>', name)
233-
return '%s%s%s <%s>' % (quotes, name, quotes, address)
234-
return address
235-
236223
# vim: set et sts=4 sw=4 :

roundup/mailgw.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,6 +1347,7 @@ def pgp_role():
13471347
else:
13481348
nodeid = cl.create(**props)
13491349
except (TypeError, IndexError, ValueError, exceptions.Reject), message:
1350+
raise
13501351
raise MailUsageError, _("""
13511352
There was a problem with the message you sent:
13521353
%(message)s

0 commit comments

Comments
 (0)