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
88from cStringIO import StringIO
9- from MimeWriter import MimeWriter
109
11- from roundup .rfc2822 import encode_header
1210from roundup import __version__
1311from 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
2119class 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+
2428class 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\n TO: %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 :
0 commit comments