Mercurial > p > roundup > code
diff roundup/mailgw.py @ 411:a6088556e9ba
Features and fixes.
Feature:
. Added INSTANCE_NAME to configuration - used in web and email to identify
the instance.
. Added EMAIL_SIGNATURE_POSITION to indicate where to place the roundup
signature info in e-mails.
. Some more flexibility in the mail gateway and more error handling.
. Login now takes you to the page you back to the were denied access to.
Fixed:
. Lots of bugs, thanks Roch�nd others on the devel mailing list!
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Mon, 26 Nov 2001 22:55:56 +0000 |
| parents | bdc2ea127ae9 |
| children | 831e91e23963 |
line wrap: on
line diff
--- a/roundup/mailgw.py Sun Nov 25 10:11:14 2001 +0000 +++ b/roundup/mailgw.py Mon Nov 26 22:55:56 2001 +0000 @@ -73,12 +73,12 @@ an exception, the original message is bounced back to the sender with the explanatory message given in the exception. -$Id: mailgw.py,v 1.35 2001-11-22 15:46:42 jhermann Exp $ +$Id: mailgw.py,v 1.36 2001-11-26 22:55:56 richard Exp $ ''' import string, re, os, mimetools, cStringIO, smtplib, socket, binascii, quopri -import traceback +import traceback, MimeWriter import hyperdb, date, password class MailGWError(ValueError): @@ -111,8 +111,8 @@ return Message(s) subject_re = re.compile(r'(?P<refwd>\s*\W?\s*(fwd|re)\s*\W?\s*)*' - r'\s*(\[(?P<classname>[^\d]+)(?P<nodeid>\d+)?\])' - r'\s*(?P<title>[^\[]+)(\[(?P<args>.+?)\])?', re.I) + r'\s*(\[(?P<classname>[^\d\s]+)(?P<nodeid>\d+)?\])' + r'\s*(?P<title>[^[]+)?(\[(?P<args>.+?)\])?', re.I) class MailGW: def __init__(self, instance, db): @@ -133,38 +133,31 @@ errors in a sane manner. It should be replaced if you wish to handle errors in a different manner. ''' - m = [] # in some rare cases, a particularly stuffed-up e-mail will make # its way into here... try to handle it gracefully sendto = message.getaddrlist('from') if sendto: try: - self.handle_message(message) - return + return self.handle_message(message) except MailUsageError, value: # bounce the message back to the sender with the usage message fulldoc = '\n'.join(string.split(__doc__, '\n')[2:]) sendto = [sendto[0][1]] - m = ['Subject: Failed issue tracker submission', ''] + m = [''] m.append(str(value)) m.append('\n\nMail Gateway Help\n=================') m.append(fulldoc) + m = self.bounce_message(message, sendto, m) except: # bounce the message back to the sender with the error message sendto = [sendto[0][1]] - m = ['Subject: failed issue tracker submission', ''] - # TODO as attachments? + m = [''] m.append('---- traceback of failure ----') s = cStringIO.StringIO() import traceback traceback.print_exc(None, s) m.append(s.getvalue()) - m.append('---- failed message follows ----') - try: - message.fp.seek(0) - except: - pass - m.append(message.fp.read()) + m = self.bounce_message(message, sendto, m) else: # very bad-looking message - we don't even know who sent it sendto = [self.ADMIN_EMAIL] @@ -172,25 +165,63 @@ m.append('') m.append('The mail gateway retrieved a message which has no From:') m.append('line, indicating that it is corrupt. Please check your') - m.append('mail gateway source.') + m.append('mail gateway source. Failed message is attached.') m.append('') - m.append('---- failed message follows ----') - try: - message.fp.seek(0) - except: - pass - m.append(message.fp.read()) + m = self.bounce_message(message, sendto, m, + subject='Badly formed message from mail gateway') # now send the message try: smtp = smtplib.SMTP(self.MAILHOST) - smtp.sendmail(self.ADMIN_EMAIL, sendto, '\n'.join(m)) + smtp.sendmail(self.ADMIN_EMAIL, sendto, m.getvalue()) except socket.error, value: raise MailGWError, "Couldn't send confirmation email: "\ "mailhost %s"%value except smtplib.SMTPException, value: raise MailGWError, "Couldn't send confirmation email: %s"%value + def bounce_message(self, message, sendto, error, + subject='Failed issue tracker submission'): + ''' create a message that explains the reason for the failed + issue submission to the author and attach the original + message. + ''' + msg = cStringIO.StringIO() + writer = MimeWriter.MimeWriter(msg) + writer.addheader('Subject', subject) + writer.addheader('From', '%s <%s>'% (self.instance.INSTANCE_NAME, + self.ISSUE_TRACKER_EMAIL)) + writer.addheader('To', ','.join(sendto)) + writer.addheader('MIME-Version', '1.0') + part = writer.startmultipartbody('mixed') + part = writer.nextpart() + body = part.startbody('text/plain') + body.write('\n'.join(error)) + + # reconstruct the original message + m = cStringIO.StringIO() + w = MimeWriter.MimeWriter(m) + for header in message.headers: + header_name = header.split(':')[0] + if message.getheader(header_name): + w.addheader(header_name,message.getheader(header_name)) + body = w.startbody('text/plain') + try: + message.fp.seek(0) + except: + pass + body.write(message.fp.read()) + + # attach the original message to the returned message + part = writer.nextpart() + part.addheader('Content-Disposition','attachment') + part.addheader('Content-Transfer-Encoding', '7bit') + body = part.startbody('message/rfc822') + body.write(m.getvalue()) + + writer.lastpart() + return msg + def handle_message(self, message): ''' message - a Message instance @@ -213,10 +244,9 @@ Subject was: "%s" '''%subject + + # get the classname classname = m.group('classname') - nodeid = m.group('nodeid') - title = m.group('title').strip() - subject_args = m.group('args') try: cl = self.db.getclass(classname) except KeyError: @@ -228,6 +258,29 @@ Subject was: "%s" '''%(classname, ', '.join(self.db.getclasses()), subject) + # get the optional nodeid + nodeid = m.group('nodeid') + + # title is optional too + title = m.group('title') + if title: + title = title.strip() + else: + title = '' + + # but we do need either a title or a nodeid... + if not nodeid and not title: + raise MailUsageError, ''' +I cannot match your message to a node in the database - you need to either +supply a full node identifier (with number, eg "[issue123]" or keep the +previous subject title intact so I can match that. + +Subject was: "%s" +'''%(classname, subject) + + # extract the args + subject_args = m.group('args') + # If there's no nodeid, check to see if this is a followup and # maybe someone's responded to the initial mail that created an # entry. Try to find the matching nodes with the same title, and @@ -255,21 +308,22 @@ Subject was: "%s" '''%(message, subject) + key = key.strip() try: - type = properties[key] + proptype = properties[key] except KeyError: raise MailUsageError, ''' Subject argument list refers to an invalid property: "%s" Subject was: "%s" '''%(key, subject) - if isinstance(type, hyperdb.String): - props[key] = value - if isinstance(type, hyperdb.Password): - props[key] = password.Password(value) - elif isinstance(type, hyperdb.Date): + if isinstance(proptype, hyperdb.String): + props[key] = value.strip() + if isinstance(proptype, hyperdb.Password): + props[key] = password.Password(value.strip()) + elif isinstance(proptype, hyperdb.Date): try: - props[key] = date.Date(value) + props[key] = date.Date(value.strip()) except ValueError, message: raise UsageError, ''' Subject argument list contains an invalid date for %s. @@ -277,9 +331,9 @@ Error was: %s Subject was: "%s" '''%(key, message, subject) - elif isinstance(type, hyperdb.Interval): + elif isinstance(proptype, hyperdb.Interval): try: - props[key] = date.Interval(value) + props[key] = date.Interval(value) # no strip needed except ValueError, message: raise UsageError, ''' Subject argument list contains an invalid date interval for %s. @@ -287,10 +341,10 @@ Error was: %s Subject was: "%s" '''%(key, message, subject) - elif isinstance(type, hyperdb.Link): - props[key] = value - elif isinstance(type, hyperdb.Multilink): - props[key] = value.split(',') + elif isinstance(proptype, hyperdb.Link): + props[key] = value.strip() + elif isinstance(proptype, hyperdb.Multilink): + props[key] = [x.strip() for x in value.split(',')] # # handle the users @@ -392,8 +446,8 @@ # handle the files files = [] - for (name, type, data) in attachments: - files.append(self.db.file.create(type=type, name=name, + for (name, mime_type, data) in attachments: + files.append(self.db.file.create(type=mime_type, name=name, content=data)) # now handle the db stuff @@ -433,6 +487,24 @@ props['status'] == resolved_id): props['status'] = chatting_id + # add nosy in arguments to issue's nosy list, don't replace + if props.has_key('nosy'): + n = {} + for nid in cl.get(nodeid, 'nosy'): + n[nid] = 1 + for value in props['nosy']: + if self.db.hasnode('user', value): + nid = value + else: + try: + nid = self.db.user.lookup(value) + except: + continue + if n.has_key(nid): continue + n[nid] = 1 + props['nosy'] = n.keys() + + # now apply the changes try: cl.set(nodeid, **props) except (TypeError, IndexError, ValueError), message: @@ -448,10 +520,6 @@ message_id = self.db.msg.create(author=author, recipients=recipients, date=date.Date('.'), summary=summary, content=content, files=files) - # fill out the properties with defaults where required - if properties.has_key('assignedto') and \ - not props.has_key('assignedto'): - props['assignedto'] = '1' # "admin" # pre-set the issue to unread if properties.has_key('status') and not props.has_key('status'): @@ -522,6 +590,9 @@ # # $Log: not supported by cvs2svn $ +# Revision 1.35 2001/11/22 15:46:42 jhermann +# Added module docstrings to all modules. +# # Revision 1.34 2001/11/15 10:24:27 richard # handle the case where there is no file attached #
