diff --git a/roundup/mailgw.py b/roundup/mailgw.py
index 23e294315d474aeb23fd3760766311606f6f813c..468333ec42bd4eca5deb6d89bb1ae0cb12f1aea4 100644 (file)
--- a/roundup/mailgw.py
+++ b/roundup/mailgw.py
an exception, the original message is bounced back to the sender with the
explanatory message given in the exception.
-$Id: mailgw.py,v 1.132 2003-09-08 21:27:16 jlgijsbers Exp $
+$Id: mailgw.py,v 1.139 2003-12-04 23:34:25 richard Exp $
"""
import string, re, os, mimetools, cStringIO, smtplib, socket, binascii, quopri
pass
class MailUsageHelp(Exception):
- pass
-
-class MailLoop(Exception):
- """ We've seen this message before... """
+ """ We need to send the help message to the user. """
pass
class Unauthorized(Exception):
""" Access denied """
+ pass
+
+class IgnoreMessage(Exception):
+ """ A general class of message that we should ignore. """
+ pass
+class IgnoreBulk(IgnoreMessage):
+ """ This is email from a mailing list or from a vacation program. """
+ pass
+class IgnoreLoop(IgnoreMessage):
+ """ We've seen this message before... """
+ pass
def initialiseSecurity(security):
''' Create some Permissions and Roles on the security object
"""
# 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:
- if not self.trapExceptions:
- return self.handle_message(message)
- try:
- return self.handle_message(message)
- except MailUsageHelp:
- # bounce the message back to the sender with the usage message
- fulldoc = '\n'.join(string.split(__doc__, '\n')[2:])
- sendto = [sendto[0][1]]
- m = ['']
- m.append('\n\nMail Gateway Help\n=================')
- m.append(fulldoc)
- self.mailer.bounce_message(message, sendto, m,
- subject="Mail Gateway Help")
- 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 = ['']
- m.append(str(value))
- m.append('\n\nMail Gateway Help\n=================')
- m.append(fulldoc)
- self.mailer.bounce_message(message, sendto, m)
- except Unauthorized, value:
- # just inform the user that he is not authorized
- sendto = [sendto[0][1]]
- m = ['']
- m.append(str(value))
- self.mailer.bounce_message(message, sendto, m)
- except MailLoop:
- # XXX we should use a log file here...
- return
- except:
- # bounce the message back to the sender with the error message
- # XXX we should use a log file here...
- sendto = [sendto[0][1], self.instance.config.ADMIN_EMAIL]
- m = ['']
- m.append('An unexpected error occurred during the processing')
- m.append('of your message. The tracker administrator is being')
- m.append('notified.\n')
- m.append('---- traceback of failure ----')
- s = cStringIO.StringIO()
- import traceback
- traceback.print_exc(None, s)
- m.append(s.getvalue())
- self.mailer.bounce_message(message, sendto, m)
- else:
+ sendto = message.getaddrlist('resent-from')
+ if not sendto:
+ sendto = message.getaddrlist('from')
+ if not sendto:
# very bad-looking message - we don't even know who sent it
# XXX we should use a log file here...
sendto = [self.instance.config.ADMIN_EMAIL]
m.append('')
self.mailer.bounce_message(message, sendto, m,
subject='Badly formed message from mail gateway')
+ return
+
+ # try normal message-handling
+ if not self.trapExceptions:
+ return self.handle_message(message)
+ try:
+ return self.handle_message(message)
+ except MailUsageHelp:
+ # bounce the message back to the sender with the usage message
+ fulldoc = '\n'.join(string.split(__doc__, '\n')[2:])
+ sendto = [sendto[0][1]]
+ m = ['']
+ m.append('\n\nMail Gateway Help\n=================')
+ m.append(fulldoc)
+ self.mailer.bounce_message(message, sendto, m,
+ subject="Mail Gateway Help")
+ 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 = ['']
+ m.append(str(value))
+ m.append('\n\nMail Gateway Help\n=================')
+ m.append(fulldoc)
+ self.mailer.bounce_message(message, sendto, m)
+ except Unauthorized, value:
+ # just inform the user that he is not authorized
+ sendto = [sendto[0][1]]
+ m = ['']
+ m.append(str(value))
+ self.mailer.bounce_message(message, sendto, m)
+ except IgnoreMessage:
+ # XXX we should use a log file here...
+ # do not take any action
+ # this exception is thrown when email should be ignored
+ return
+ except:
+ # bounce the message back to the sender with the error message
+ # XXX we should use a log file here...
+ sendto = [sendto[0][1], self.instance.config.ADMIN_EMAIL]
+ m = ['']
+ m.append('An unexpected error occurred during the processing')
+ m.append('of your message. The tracker administrator is being')
+ m.append('notified.\n')
+ m.append('---- traceback of failure ----')
+ s = cStringIO.StringIO()
+ import traceback
+ traceback.print_exc(None, s)
+ m.append(s.getvalue())
+ self.mailer.bounce_message(message, sendto, m)
def get_part_data_decoded(self,part):
encoding = part.getencoding()
'''
# detect loops
if message.getheader('x-roundup-loop', ''):
- raise MailLoop
+ raise IgnoreLoop
+
+ # detect Precedence: Bulk
+ if (message.getheader('precedence', '') == 'bulk'):
+ raise IgnoreBulk
# XXX Don't enable. This doesn't work yet.
# "[^A-z.]tracker\+(?P<classname>[^\d\s]+)(?P<nodeid>\d+)\@some.dom.ain[^A-z.]"
# nodeid = issue.group('nodeid')
# break
+ # determine the sender's address
+ from_list = message.getaddrlist('resent-from')
+ if not from_list:
+ from_list = message.getaddrlist('from')
+
# handle the subject line
subject = message.getheader('subject', '')
self.db.confirm_registration(otk.group('otk'))
subject = 'Your registration to %s is complete' % \
self.instance.config.TRACKER_NAME
- sendto = message.getaddrlist('from')[0][1]
+ sendto = [from_list[0][1]]
self.mailer.standard_message(sendto, subject, '')
return
elif hasattr(self.instance.config, 'MAIL_DEFAULT_CLASS') and \
# ok, now figure out who the author is - create a new user if the
# "create" flag is true
- author = uidFromAddress(self.db, message.getaddrlist('from')[0],
- create=create)
+ author = uidFromAddress(self.db, from_list[0], create=create)
# if we're not recognised, and we don't get added as a user, then we
# must be anonymous
You are not a registered user.
Unknown address: %s
-'''%message.getaddrlist('from')[0][1]
+'''%from_list[0][1]
else:
# we're registered and we're _still_ not allowed access
raise Unauthorized, 'You are not permitted to access '\
#
# handle the attachments
#
- files = []
- for (name, mime_type, data) in attachments:
- if not name:
- name = "unnamed"
- files.append(self.db.file.create(type=mime_type, name=name,
- content=data, **file_props))
- # attach the files to the issue
- if nodeid:
- # extend the existing files list
- fileprop = cl.get(nodeid, 'files')
- fileprop.extend(files)
- props['files'] = fileprop
- else:
- # pre-load the files list
- props['files'] = files
-
+ if properties.has_key('files'):
+ files = []
+ for (name, mime_type, data) in attachments:
+ if not name:
+ name = "unnamed"
+ files.append(self.db.file.create(type=mime_type, name=name,
+ content=data, **file_props))
+ # attach the files to the issue
+ if nodeid:
+ # extend the existing files list
+ fileprop = cl.get(nodeid, 'files')
+ fileprop.extend(files)
+ props['files'] = fileprop
+ else:
+ # pre-load the files list
+ props['files'] = files
#
# create the message if there's a message body (content)
#
- if content:
+ if (content and properties.has_key('messages')):
message_id = self.db.msg.create(author=author,
recipients=recipients, date=date.Date('.'), summary=summary,
content=content, files=files, messageid=messageid,
return nodeid
-def setPropArrayFromString(self, cl, propString, nodeid = None):
+def setPropArrayFromString(self, cl, propString, nodeid=None):
''' takes string of form prop=value,value;prop2=value
and returns (error, prop[..])
'''
- properties = cl.getprops()
props = {}
errors = []
for prop in string.split(propString, ';'):
errors.append('not of form [arg=value,value,...;'
'arg=value,value,...]')
return (errors, props)
-
- # ensure it's a valid property name
+ # convert the value to a hyperdb-usable value
propname = propname.strip()
try:
- proptype = properties[propname]
- except KeyError:
- errors.append('refers to an invalid property: "%s"'%propname)
- continue
-
- # convert the string value to a real property value
- if isinstance(proptype, hyperdb.String):
- props[propname] = value.strip()
- if isinstance(proptype, hyperdb.Password):
- props[propname] = password.Password(value.strip())
- elif isinstance(proptype, hyperdb.Date):
- try:
- props[propname] = date.Date(value.strip()).local(self.db.getUserTimezone())
- except ValueError, message:
- errors.append('contains an invalid date for %s.'%propname)
- elif isinstance(proptype, hyperdb.Interval):
- try:
- props[propname] = date.Interval(value)
- except ValueError, message:
- errors.append('contains an invalid date interval for %s.'%
- propname)
- elif isinstance(proptype, hyperdb.Link):
- linkcl = self.db.classes[proptype.classname]
- propkey = linkcl.labelprop(default_to_id=1)
- try:
- props[propname] = linkcl.lookup(value)
- except KeyError, message:
- errors.append('"%s" is not a value for %s.'%(value, propname))
- elif isinstance(proptype, hyperdb.Multilink):
- # get the linked class
- linkcl = self.db.classes[proptype.classname]
- propkey = linkcl.labelprop(default_to_id=1)
- if nodeid:
- curvalue = cl.get(nodeid, propname)
- else:
- curvalue = []
-
- # handle each add/remove in turn
- # keep an extra list for all items that are
- # definitely in the new list (in case of e.g.
- # <propname>=A,+B, which should replace the old
- # list with A,B)
- set = 0
- newvalue = []
- for item in value.split(','):
- item = item.strip()
-
- # handle +/-
- remove = 0
- if item.startswith('-'):
- remove = 1
- item = item[1:]
- elif item.startswith('+'):
- item = item[1:]
- else:
- set = 1
-
- # look up the value
- try:
- item = linkcl.lookup(item)
- except KeyError, message:
- errors.append('"%s" is not a value for %s.'%(item,
- propname))
- continue
-
- # perform the add/remove
- if remove:
- try:
- curvalue.remove(item)
- except ValueError:
- errors.append('"%s" is not currently in for %s.'%(item,
- propname))
- continue
- else:
- newvalue.append(item)
- if item not in curvalue:
- curvalue.append(item)
-
- # that's it, set the new Multilink property value,
- # or overwrite it completely
- if set:
- props[propname] = newvalue
- else:
- props[propname] = curvalue
- elif isinstance(proptype, hyperdb.Boolean):
- value = value.strip()
- props[propname] = value.lower() in ('yes', 'true', 'on', '1')
- elif isinstance(proptype, hyperdb.Number):
- value = value.strip()
- props[propname] = float(value)
+ props[propname] = hyperdb.rawToHyperdb(self.db, cl, nodeid,
+ propname, value)
+ except hyperdb.HyperdbValueError, message:
+ errors.append(message)
return errors, props
def parseContent(content, keep_citations, keep_body,
blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'),
eol=re.compile(r'[\r\n]+'),
- signature=re.compile(r'^[>|\s]*[-_]+\s*$'),
+ signature=re.compile(r'^[>|\s]*-- ?$'),
original_msg=re.compile(r'^[>|\s]*-----\s?Original Message\s?-----$')):
''' The message body is divided into sections by blank lines.
Sections where the second and all subsequent lines begin with a ">"