Code

migrate from MimeWriter to email
[roundup.git] / roundup / mailer.py
index 09f808ca4dfddb0e82a8199e69ec1d26f8a6e946..9c70852317f36fd6a35f087d65d8d11c482f7b95 100644 (file)
@@ -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 :