diff --git a/roundup/mailgw.py b/roundup/mailgw.py
index e6d66cf9c3786ac28ce756d17aca19a1cb28ddb3..19151b2108934e9c711511fe468dcfcd023e0a7c 100644 (file)
--- a/roundup/mailgw.py
+++ b/roundup/mailgw.py
+#
+# Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
+# This module is free software, and you may redistribute it and/or modify
+# under the same terms as Python, so long as this copyright message and
+# disclaimer are retained in their original form.
+#
+# IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
+# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
+# OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
+# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
+# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+#
'''
An e-mail gateway for Roundup.
an exception, the original message is bounced back to the sender with the
explanatory message given in the exception.
-$Id: mailgw.py,v 1.6 2001-08-01 04:24:21 richard Exp $
+$Id: mailgw.py,v 1.15 2001-08-30 06:01:17 richard Exp $
'''
import string, re, os, mimetools, cStringIO, smtplib, socket, binascii, quopri
import traceback
-import date
+import hyperdb, date
+
+class MailUsageError(ValueError):
+ pass
class Message(mimetools.Message):
''' subclass mimetools.Message so we can retrieve the parts of the
'''
# ok, figure the subject, author, recipients and content-type
message = Message(fp)
+ m = []
try:
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 = [message.getaddrlist('from')[0][1]]
+ m = ['Subject: Failed issue tracker submission', '']
+ m.append(str(value))
+ m.append('\nMail Gateway Help\n=================')
+ m.append(fulldoc)
except:
# bounce the message back to the sender with the error message
sendto = [message.getaddrlist('from')[0][1]]
except:
pass
m.append(fp.read())
+ if m:
try:
smtp = smtplib.SMTP(self.MAILHOST)
smtp.sendmail(self.ADMIN_EMAIL, sendto, '\n'.join(m))
Parse the message as per the module docstring.
'''
# handle the subject line
- m = subject_re.match(message.getheader('subject'))
+ subject = message.getheader('subject', '')
+ m = subject_re.match(subject)
if not m:
- raise ValueError, 'No [designator] found in subject "%s"'
+ raise MailUsageError, '''
+The message you sent to roundup did not contain a properly formed subject
+line. The subject must contain a class name or designator to indicate the
+"topic" of the message. For example:
+ Subject: [issue] This is a new issue
+ - this will create a new issue in the tracker with the title "This is
+ a new issue".
+ Subject: [issue1234] This is a followup to issue 1234
+ - this will append the message's contents to the existing issue 1234
+ in the tracker.
+
+Subject was: "%s"
+'''%subject
classname = m.group('classname')
nodeid = m.group('nodeid')
title = m.group('title').strip()
subject_args = m.group('args')
- cl = self.db.getclass(classname)
+ try:
+ cl = self.db.getclass(classname)
+ except KeyError:
+ raise MailUsageError, '''
+The class name you identified in the subject line ("%s") does not exist in the
+database.
+
+Valid class names are: %s
+Subject was: "%s"
+'''%(classname, ', '.join(self.db.getclasses()), subject)
properties = cl.getprops()
props = {}
args = m.group('args')
try:
key, value = prop.split('=')
except ValueError, message:
- raise ValueError, 'Args list not of form [arg=value,value,...;arg=value,value,value..] (specific exception message was "%s")'%message
- type = properties[key]
- if type.isStringType:
+ raise MailUsageError, '''
+Subject argument list not of form [arg=value,value,...;arg=value,value...]
+ (specific exception message was "%s")
+
+Subject was: "%s"
+'''%(message, subject)
+ try:
+ type = 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
- elif type.isDateType:
+ elif isinstance(type, hyperdb.Date):
props[key] = date.Date(value)
- elif type.isIntervalType:
+ elif isinstance(type, hyperdb.Interval):
props[key] = date.Interval(value)
- elif type.isLinkType:
+ elif isinstance(type, hyperdb.Link):
props[key] = value
- elif type.isMultilinkType:
+ elif isinstance(type, hyperdb.Multilink):
props[key] = value.split(',')
# handle the users
attachments.append((name, part.gettype(), data))
if content is None:
- raise ValueError, 'No text/plain part found'
+ raise MailUsageError, '''
+Roundup requires the submission to be plain text. The message parser could
+not find a text/plain part o use.
+'''
elif content_type[:10] == 'multipart/':
# skip over the intro to the first boundary
# this one's our content
content = part.fp.read()
if content is None:
- raise ValueError, 'No text/plain part found'
+ raise MailUsageError, '''
+Roundup requires the submission to be plain text. The message parser could
+not find a text/plain part o use.
+'''
elif content_type != 'text/plain':
- raise ValueError, 'No text/plain part found'
+ raise MailUsageError, '''
+Roundup requires the submission to be plain text. The message parser could
+not find a text/plain part o use.
+'''
else:
content = message.fp.read()
- # extract out the summary from the message
- summary = []
- for line in content.split('\n'):
- line = line.strip()
- if summary and not line:
- break
- if not line:
- summary.append('')
- elif line[0] not in '>|':
- summary.append(line)
- summary = '\n'.join(summary)
+ summary, content = parseContent(content)
# handle the files
files = []
message_id = self.db.msg.create(author=author,
recipients=recipients, date=date.Date('.'), summary=summary,
content=content, files=files)
- messages = cl.get(nodeid, 'messages')
+ try:
+ messages = cl.get(nodeid, 'messages')
+ except IndexError:
+ raise MailUsageError, '''
+The node specified by the designator in the subject of your message ("%s")
+does not exist.
+
+Subject was: "%s"
+'''%(nodeid, subject)
messages.append(message_id)
props['messages'] = messages
cl.set(nodeid, **props)
props['nosy'].sort()
nodeid = cl.create(**props)
+def parseContent(content, blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'),
+ eol=re.compile(r'[\r\n]+'), signature=re.compile(r'^[>|\s]*[-_]+\s*$')):
+ ''' The message body is divided into sections by blank lines.
+ Sections where the second and all subsequent lines begin with a ">" or "|"
+ character are considered "quoting sections". The first line of the first
+ non-quoting section becomes the summary of the message.
+ '''
+ sections = blank_line.split(content)
+ # extract out the summary from the message
+ summary = ''
+ l = []
+ for section in sections:
+ section = section.strip()
+ if not section:
+ continue
+ lines = eol.split(section)
+ if lines[0] and lines[0][0] in '>|':
+ continue
+ if len(lines) > 1 and lines[1] and lines[1][0] in '>|':
+ continue
+ if not summary:
+ summary = lines[0]
+ l.append(section)
+ continue
+ if signature.match(lines[0]):
+ break
+ l.append(section)
+ return summary, '\n'.join(l)
+
#
# $Log: not supported by cvs2svn $
+# Revision 1.14 2001/08/13 23:02:54 richard
+# Make the mail parser a little more robust.
+#
+# Revision 1.13 2001/08/12 06:32:36 richard
+# using isinstance(blah, Foo) now instead of isFooType
+#
+# Revision 1.12 2001/08/08 01:27:00 richard
+# Added better error handling to mailgw.
+#
+# Revision 1.11 2001/08/08 00:08:03 richard
+# oops ;)
+#
+# Revision 1.10 2001/08/07 00:24:42 richard
+# stupid typo
+#
+# Revision 1.9 2001/08/07 00:15:51 richard
+# Added the copyright/license notice to (nearly) all files at request of
+# Bizar Software.
+#
+# Revision 1.8 2001/08/05 07:06:07 richard
+# removed some print statements
+#
+# Revision 1.7 2001/08/03 07:18:22 richard
+# Implemented correct mail splitting (was taking a shortcut). Added unit
+# tests. Also snips signatures now too.
+#
+# Revision 1.6 2001/08/01 04:24:21 richard
+# mailgw was assuming certain properties existed on the issues being created.
+#
# Revision 1.5 2001/07/29 07:01:39 richard
# Added vim command to all source so that we don't get no steenkin' tabs :)
#