Mercurial > p > roundup > code
comparison roundup/mailer.py @ 1799:071ea6fc803f
Extracted duplicated mail-sending code...
...from mailgw, roundupdb and client.py to the new mailer.py module.
| author | Johannes Gijsbers <jlgijsbers@users.sourceforge.net> |
|---|---|
| date | Mon, 08 Sep 2003 09:28:28 +0000 |
| parents | |
| children | ee33ce4987f5 |
comparison
equal
deleted
inserted
replaced
| 1798:9bd553ea75fa | 1799:071ea6fc803f |
|---|---|
| 1 """Sending Roundup-specific mail over SMTP.""" | |
| 2 # $Id: mailer.py,v 1.1 2003-09-08 09:28:28 jlgijsbers Exp $ | |
| 3 | |
| 4 import time, quopri, os, socket, smtplib, re | |
| 5 | |
| 6 from cStringIO import StringIO | |
| 7 from MimeWriter import MimeWriter | |
| 8 | |
| 9 from roundup.rfc2822 import encode_header | |
| 10 | |
| 11 class MessageSendError(RuntimeError): | |
| 12 pass | |
| 13 | |
| 14 class Mailer: | |
| 15 """Roundup-specific mail sending.""" | |
| 16 def __init__(self, config): | |
| 17 self.config = config | |
| 18 | |
| 19 # set to indicate to roundup not to actually _send_ email | |
| 20 # this var must contain a file to write the mail to | |
| 21 self.debug = os.environ.get('SENDMAILDEBUG', '') | |
| 22 | |
| 23 def get_standard_message(self, to, subject, author=None): | |
| 24 if not author: | |
| 25 author = straddr((self.config.TRACKER_NAME, | |
| 26 self.config.ADMIN_EMAIL)) | |
| 27 message = StringIO() | |
| 28 writer = MimeWriter(message) | |
| 29 writer.addheader('Subject', encode_header(subject)) | |
| 30 writer.addheader('To', to) | |
| 31 writer.addheader('From', author) | |
| 32 writer.addheader('Date', time.strftime("%a, %d %b %Y %H:%M:%S +0000", | |
| 33 time.gmtime())) | |
| 34 | |
| 35 # Add a unique Roundup header to help filtering | |
| 36 writer.addheader('X-Roundup-Name', self.config.TRACKER_NAME) | |
| 37 # and another one to avoid loops | |
| 38 writer.addheader('X-Roundup-Loop', 'hello') | |
| 39 | |
| 40 writer.addheader('MIME-Version', '1.0') | |
| 41 | |
| 42 return message, writer | |
| 43 | |
| 44 def standard_message(self, to, subject, content): | |
| 45 message, writer = self.get_standard_message(to, subject) | |
| 46 | |
| 47 writer.addheader('Content-Transfer-Encoding', 'quoted-printable') | |
| 48 body = writer.startbody('text/plain; charset=utf-8') | |
| 49 content = StringIO(content) | |
| 50 quopri.encode(content, body, 0) | |
| 51 | |
| 52 self.smtp_send(to, message) | |
| 53 | |
| 54 def bounce_message(self, bounced_message, to, error, | |
| 55 subject='Failed issue tracker submission'): | |
| 56 message, writer = self.get_standard_message(', '.join(to), subject) | |
| 57 | |
| 58 part = writer.startmultipartbody('mixed') | |
| 59 part = writer.nextpart() | |
| 60 part.addheader('Content-Transfer-Encoding', 'quoted-printable') | |
| 61 body = part.startbody('text/plain; charset=utf-8') | |
| 62 body.write('\n'.join(error)) | |
| 63 | |
| 64 # attach the original message to the returned message | |
| 65 part = writer.nextpart() | |
| 66 part.addheader('Content-Disposition', 'attachment') | |
| 67 part.addheader('Content-Description', 'Message you sent') | |
| 68 body = part.startbody('text/plain') | |
| 69 | |
| 70 for header in bounced_message.headers: | |
| 71 body.write(header) | |
| 72 body.write('\n') | |
| 73 try: | |
| 74 bounced_message.rewindbody() | |
| 75 except IOError, message: | |
| 76 body.write("*** couldn't include message body: %s ***" | |
| 77 % bounced_message) | |
| 78 else: | |
| 79 body.write(bounced_message.fp.read()) | |
| 80 | |
| 81 writer.lastpart() | |
| 82 | |
| 83 self.smtp_send(to, message) | |
| 84 | |
| 85 def smtp_send(self, to, message): | |
| 86 if self.debug: | |
| 87 # don't send - just write to a file | |
| 88 open(self.debug, 'a').write('FROM: %s\nTO: %s\n%s\n' % | |
| 89 (self.config.ADMIN_EMAIL, | |
| 90 ', '.join(to), | |
| 91 message.getvalue())) | |
| 92 else: | |
| 93 # now try to send the message | |
| 94 try: | |
| 95 # send the message as admin so bounces are sent there | |
| 96 # instead of to roundup | |
| 97 smtp = SMTPConnection(self.config) | |
| 98 smtp.sendmail(self.config.ADMIN_EMAIL, [to], | |
| 99 message.getvalue()) | |
| 100 except socket.error, value: | |
| 101 raise MessageSendError("Error: couldn't send email: " | |
| 102 "mailhost %s"%value) | |
| 103 except smtplib.SMTPException, msg: | |
| 104 raise MessageSendError("Error: couldn't send email: %s"%msg) | |
| 105 | |
| 106 class SMTPConnection(smtplib.SMTP): | |
| 107 ''' Open an SMTP connection to the mailhost specified in the config | |
| 108 ''' | |
| 109 def __init__(self, config): | |
| 110 | |
| 111 smtplib.SMTP.__init__(self, config.MAILHOST) | |
| 112 | |
| 113 # use TLS? | |
| 114 use_tls = getattr(config, 'MAILHOST_TLS', 'no') | |
| 115 if use_tls == 'yes': | |
| 116 # do we have key files too? | |
| 117 keyfile = getattr(config, 'MAILHOST_TLS_KEYFILE', '') | |
| 118 if keyfile: | |
| 119 certfile = getattr(config, 'MAILHOST_TLS_CERTFILE', '') | |
| 120 if certfile: | |
| 121 args = (keyfile, certfile) | |
| 122 else: | |
| 123 args = (keyfile, ) | |
| 124 else: | |
| 125 args = () | |
| 126 # start the TLS | |
| 127 self.starttls(*args) | |
| 128 | |
| 129 # ok, now do we also need to log in? | |
| 130 mailuser = getattr(config, 'MAILUSER', None) | |
| 131 if mailuser: | |
| 132 self.login(*config.MAILUSER) | |
| 133 | |
| 134 # use the 'email' module, either imported, or our copied version | |
| 135 try : | |
| 136 from email.Utils import formataddr as straddr | |
| 137 except ImportError : | |
| 138 # code taken from the email package 2.4.3 | |
| 139 def straddr(pair, specialsre = re.compile(r'[][\()<>@,:;".]'), | |
| 140 escapesre = re.compile(r'[][\()"]')): | |
| 141 name, address = pair | |
| 142 if name: | |
| 143 quotes = '' | |
| 144 if specialsre.search(name): | |
| 145 quotes = '"' | |
| 146 name = escapesre.sub(r'\\\g<0>', name) | |
| 147 return '%s%s%s <%s>' % (quotes, name, quotes, address) | |
| 148 return address |
