diff --git a/roundup/mailgw.py b/roundup/mailgw.py
index 7057597e0f8de790f55f195932e4cda842ad7579..8b4f95e6de567160cee5a5f0b85788fb3f455a9e 100644 (file)
--- a/roundup/mailgw.py
+++ b/roundup/mailgw.py
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-__doc__ = '''
+'''
An e-mail gateway for Roundup.
Incoming messages are examined for multiple parts:
an exception, the original message is bounced back to the sender with the
explanatory message given in the exception.
-$Id: mailgw.py,v 1.87 2002-09-11 01:19:16 richard Exp $
+$Id: mailgw.py,v 1.99 2002-11-05 22:59:46 richard Exp $
'''
import string, re, os, mimetools, cStringIO, smtplib, socket, binascii, quopri
-import time, random
+import time, random, sys
import traceback, MimeWriter
import hyperdb, date, password
s.seek(0)
return Message(s)
-subject_re = re.compile(r'(?P<refwd>\s*\W?\s*(fwd|re|aw)\s*\W?\s*)*'
- r'\s*(\[(?P<classname>[^\d\s]+)(?P<nodeid>\d+)?\])?'
- r'\s*(?P<title>[^[]+)?(\[(?P<args>.+?)\])?', re.I)
+subject_re = re.compile(r'(?P<refwd>\s*\W?\s*(fwd|re|aw)\W\s*)*'
+ r'\s*(?P<quote>")?(\[(?P<classname>[^\d\s]+)(?P<nodeid>\d+)?\])?'
+ r'\s*(?P<title>[^[]+)?"?(\[(?P<args>.+?)\])?', re.I)
class MailGW:
def __init__(self, instance, db):
def do_pipe(self):
''' Read a message from standard input and pass it to the mail handler.
+
+ Read into an internal structure that we can seek on (in case
+ there's an error).
+
+ XXX: we may want to read this into a temporary file instead...
'''
- self.main(sys.stdin)
+ s = cStringIO.StringIO()
+ s.write(sys.stdin.read())
+ s.seek(0)
+ self.main(s)
return 0
def do_mailbox(self, filename):
body = part.startbody('text/plain')
body.write('\n'.join(error))
- # reconstruct the original message
- m = cStringIO.StringIO()
- w = MimeWriter.MimeWriter(m)
- # default the content_type, just in case...
- content_type = 'text/plain'
- # add the headers except the content-type
+ # 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 message.headers:
- header_name = header.split(':')[0]
- if header_name.lower() == 'content-type':
- content_type = message.getheader(header_name)
- elif message.getheader(header_name):
- w.addheader(header_name, message.getheader(header_name))
- # now attach the message body
- body = w.startbody(content_type)
+ body.write(header)
+ body.write('\n')
try:
message.rewindbody()
- except IOError:
- body.write("*** couldn't include message body: read from pipe ***")
+ except IOError, message:
+ body.write("*** couldn't include message body: %s ***"%message)
else:
body.write(message.fp.read())
- # attach the original message to the returned message
- part = writer.nextpart()
- part.addheader('Content-Disposition','attachment')
- part.addheader('Content-Description','Message you sent')
- part.addheader('Content-Transfer-Encoding', '7bit')
- body = part.startbody('message/rfc822')
- body.write(m.getvalue())
-
writer.lastpart()
return msg
else:
title = ''
+ # strip off the quotes that dumb emailers put around the subject, like
+ # Re: "[issue1] bla blah"
+ if m.group('quote') and title.endswith('"'):
+ title = title[:-1]
+
# but we do need either a title or a nodeid...
if nodeid is None and not title:
raise MailUsageError, '''
author = uidFromAddress(self.db, message.getaddrlist('from')[0],
create=create)
- # no author? means we're not author
+ # if we're not recognised, and we don't get added as a user, then we
+ # must be anonymous
if not author:
- raise Unauthorized, '''
+ author = anonid
+
+ # make sure the author has permission to use the email interface
+ if not self.db.security.hasPermission('Email Access', author):
+ if author == anonid:
+ # we're anonymous and we need to be a registered user
+ raise Unauthorized, '''
You are not a registered user.
Unknown address: %s
'''%message.getaddrlist('from')[0][1]
-
- # make sure the author has permission to use the email interface
- if not self.db.security.hasPermission('Email Access', author):
- raise Unauthorized, 'You are not permitted to access this tracker.'
+ else:
+ # we're registered and we're _still_ not allowed access
+ raise Unauthorized, 'You are not permitted to access '\
+ 'this tracker.'
# make sure they're allowed to edit this class of information
if not self.db.security.hasPermission('Edit', author, classname):
# reopen the database as the author
username = self.db.user.get(author, 'username')
+ self.db.close()
self.db = self.instance.open(username)
# re-get the class with the new database connection
# try the user alternate addresses if possible
props = db.user.getprops()
if props.has_key('alternate_addresses'):
- users = db.user.filter(None, {'alternate_addresses': address},
- [], [])
+ users = db.user.filter(None, {'alternate_addresses': address})
user = extractUserFromList(db.user, users)
if user is not None: return user
# see if there's a response somewhere inside this section (ie.
# no blank line between quoted message and response)
for line in lines[1:]:
- if line[0] not in '>|':
+ if line and line[0] not in '>|':
break
else:
# we keep quoted bits if specified in the config
l.append(section)
continue
# keep this section - it has reponse stuff in it
- if not summary:
- # and while we're at it, use the first non-quoted bit as
- # our summary
- summary = line
lines = lines[lines.index(line):]
section = '\n'.join(lines)
+ # and while we're at it, use the first non-quoted bit as
+ # our summary
+ summary = section
if not summary:
# if we don't have our summary yet use the first line of this
# section
- summary = lines[0]
+ summary = section
elif signature.match(lines[0]) and 2 <= len(lines) <= 10:
# lose any signature
break
# and add the section to the output
l.append(section)
+ # figure the summary - find the first sentence-ending punctuation or the
+ # first whole line, whichever is longest
+ sentence = re.search(r'^([^!?\.]+[!?\.])', summary)
+ if sentence:
+ sentence = sentence.group(1)
+ else:
+ sentence = ''
+ first = eol.split(summary)[0]
+ summary = max(sentence, first)
+
# Now reconstitute the message content minus the bits we don't care
# about.
if not keep_body: