X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=roundup%2Fmailer.py;fp=roundup%2Fmailer.py;h=9c70852317f36fd6a35f087d65d8d11c482f7b95;hb=349dcdaf535cfdc53b4d00f3a0d85bf3a5f97571;hp=09f808ca4dfddb0e82a8199e69ec1d26f8a6e946;hpb=a995573a382c85a9438c40cb1962db72fd28ca3f;p=roundup.git diff --git a/roundup/mailer.py b/roundup/mailer.py index 09f808c..9c70852 100644 --- a/roundup/mailer.py +++ b/roundup/mailer.py @@ -3,24 +3,28 @@ __docformat__ = 'restructuredtext' # $Id: mailer.py,v 1.22 2008-07-21 01:44:58 richard Exp $ -import time, quopri, os, socket, smtplib, re, sys, traceback +import time, quopri, os, socket, smtplib, re, sys, traceback, email from cStringIO import StringIO -from MimeWriter import MimeWriter -from roundup.rfc2822 import encode_header from roundup import __version__ from roundup.date import get_timezone -try: - from email.Utils import formatdate -except ImportError: - def formatdate(): - return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()) +from email.Utils import formatdate, formataddr +from email.Message import Message +from email.Header import Header +from email.MIMEText import MIMEText +from email.MIMEMultipart import MIMEMultipart class MessageSendError(RuntimeError): pass +def encode_quopri(msg): + orig = msg.get_payload() + encdata = quopri.encodestring(orig) + msg.set_payload(encdata) + msg['Content-Transfer-Encoding'] = 'quoted-printable' + class Mailer: """Roundup-specific mail sending.""" def __init__(self, config): @@ -41,7 +45,7 @@ class Mailer: os.environ['TZ'] = get_timezone(self.config.TIMEZONE).tzname(None) time.tzset() - def get_standard_message(self, to, subject, author=None): + def get_standard_message(self, to, subject, author=None, multipart=False): '''Form a standard email message from Roundup. "to" - recipients list @@ -55,38 +59,48 @@ class Mailer: ''' # encode header values if they need to be charset = getattr(self.config, 'EMAIL_CHARSET', 'utf-8') - tracker_name = self.config.TRACKER_NAME - if charset != 'utf-8': - tracker = unicode(tracker_name, 'utf-8').encode(charset) + tracker_name = unicode(self.config.TRACKER_NAME, 'utf-8') if not author: - author = straddr((tracker_name, self.config.ADMIN_EMAIL)) + author = formataddr((tracker_name, self.config.ADMIN_EMAIL)) + else: + name = unicode(author[0], 'utf-8') + author = formataddr((name, author[1])) + + if multipart: + message = MIMEMultipart() else: - name = author[0] - if charset != 'utf-8': - name = unicode(name, 'utf-8').encode(charset) - author = straddr((encode_header(name, charset), author[1])) - - message = StringIO() - writer = MimeWriter(message) - writer.addheader('Subject', encode_header(subject, charset)) - writer.addheader('To', ', '.join(to)) - writer.addheader('From', author) - writer.addheader('Date', formatdate(localtime=True)) + message = Message() + message.set_charset(charset) + message['Content-Type'] = 'text/plain; charset="%s"'%charset + + try: + message['Subject'] = subject.encode('ascii') + except UnicodeError: + message['Subject'] = Header(subject, charset) + message['To'] = ', '.join(to) + try: + message['From'] = author.encode('ascii') + except UnicodeError: + message['From'] = Header(author, charset) + message['Date'] = formatdate(localtime=True) # add a Precedence header so autoresponders ignore us - writer.addheader('Precedence', 'bulk') + message['Precedence'] = 'bulk' # Add a unique Roundup header to help filtering - writer.addheader('X-Roundup-Name', encode_header(tracker_name, - charset)) + try: + message['X-Roundup-Name'] = tracker_name.encode('ascii') + except UnicodeError: + message['X-Roundup-Name'] = Header(tracker_name, charset) + # and another one to avoid loops - writer.addheader('X-Roundup-Loop', 'hello') + message['X-Roundup-Loop'] = 'hello' # finally, an aid to debugging problems - writer.addheader('X-Roundup-Version', __version__) + message['X-Roundup-Version'] = __version__ - writer.addheader('MIME-Version', '1.0') + message['MIME-Version'] = '1.0' - return message, writer + return message def standard_message(self, to, subject, content, author=None): """Send a standard message. @@ -96,15 +110,12 @@ class Mailer: - subject: the subject as a string. - content: the body of the message as a string. - author: the sender as a (name, address) tuple - """ - message, writer = self.get_standard_message(to, subject, author) - - writer.addheader('Content-Transfer-Encoding', 'quoted-printable') - body = writer.startbody('text/plain; charset=utf-8') - content = StringIO(content) - quopri.encode(content, body, 0) - self.smtp_send(to, message) + All strings are assumed to be UTF-8 encoded. + """ + message = self.get_standard_message(to, subject, author) + message.set_payload(content) + self.smtp_send(to, str(message)) def bounce_message(self, bounced_message, to, error, subject='Failed issue tracker submission'): @@ -128,23 +139,13 @@ class Mailer: elif error_messages_to == "both": to.append(dispatcher_email) - message, writer = self.get_standard_message(to, subject) + message = self.get_standard_message(to, subject) - part = writer.startmultipartbody('mixed') - part = writer.nextpart() - part.addheader('Content-Transfer-Encoding', 'quoted-printable') - body = part.startbody('text/plain; charset=utf-8') - body.write(quopri.encodestring ('\n'.join(error))) + # add the error text + part = MIMEText(error) + message.attach(part) # attach the original message to the returned message - part = writer.nextpart() - part.addheader('Content-Disposition', 'attachment') - part.addheader('Content-Description', 'Message you sent') - body = part.startbody('text/plain') - - for header in bounced_message.headers: - body.write(header) - body.write('\n') try: bounced_message.rewindbody() except IOError, message: @@ -152,11 +153,15 @@ class Mailer: % bounced_message) else: body.write(bounced_message.fp.read()) + part = MIMEText(bounced_message.fp.read()) + part['Content-Disposition'] = 'attachment' + for header in bounced_message.headers: + part.write(header) + message.attach(part) - writer.lastpart() - + # send try: - self.smtp_send(to, message) + self.smtp_send(to, str(message)) except MessageSendError: # squash mail sending errors when bouncing mail # TODO this *could* be better, as we could notify admin of the @@ -184,16 +189,14 @@ class Mailer: # don't send - just write to a file open(self.debug, 'a').write('FROM: %s\nTO: %s\n%s\n' % (self.config.ADMIN_EMAIL, - ', '.join(to), - message.getvalue())) + ', '.join(to), message)) else: # now try to send the message try: # send the message as admin so bounces are sent there # instead of to roundup smtp = SMTPConnection(self.config) - smtp.sendmail(self.config.ADMIN_EMAIL, to, - message.getvalue()) + smtp.sendmail(self.config.ADMIN_EMAIL, to, message) except socket.error, value: raise MessageSendError("Error: couldn't send email: " "mailhost %s"%value) @@ -217,20 +220,4 @@ class SMTPConnection(smtplib.SMTP): if mailuser: self.login(mailuser, config["MAIL_PASSWORD"]) -# use the 'email' module, either imported, or our copied version -try : - from email.Utils import formataddr as straddr -except ImportError : - # code taken from the email package 2.4.3 - def straddr(pair, specialsre = re.compile(r'[][\()<>@,:;".]'), - escapesre = re.compile(r'[][\()"]')): - name, address = pair - if name: - quotes = '' - if specialsre.search(name): - quotes = '"' - name = escapesre.sub(r'\\\g<0>', name) - return '%s%s%s <%s>' % (quotes, name, quotes, address) - return address - # vim: set et sts=4 sw=4 :