X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=roundup%2Fmailgw.py;h=4c99afb0a22de18a65b7283dbb4032c2a92a10e6;hb=f04ca7e8c6067e05296508ed2b7c302425ffbcc8;hp=9a3083adfd49b628a1ab9fb5c064fc08638be81e;hpb=d8a93d0153e47a1c30506f5be857e1698a48977d;p=roundup.git diff --git a/roundup/mailgw.py b/roundup/mailgw.py index 9a3083a..4c99afb 100644 --- a/roundup/mailgw.py +++ b/roundup/mailgw.py @@ -73,7 +73,7 @@ are calling the create() method to create a new node). If an auditor raises an exception, the original message is bounced back to the sender with the explanatory message given in the exception. -$Id: mailgw.py,v 1.63 2002-02-12 08:08:55 grubert Exp $ +$Id: mailgw.py,v 1.73 2002-05-22 04:12:05 richard Exp $ ''' @@ -120,7 +120,7 @@ class Message(mimetools.Message): return Message(s) subject_re = re.compile(r'(?P\s*\W?\s*(fwd|re|aw)\s*\W?\s*)*' - r'\s*(\[(?P[^\d\s]+)(?P\d+)?\])' + r'\s*(\[(?P[^\d\s]+)(?P\d+)?\])?' r'\s*(?P[^[]+)?(\[(?P<args>.+?)\])?', re.I) class MailGW: @@ -131,7 +131,7 @@ class MailGW: def main(self, fp): ''' fp - the file from which to read the Message. ''' - self.handle_Message(Message(fp)) + return self.handle_Message(Message(fp)) def handle_Message(self, message): '''Handle an RFC822 Message @@ -292,6 +292,20 @@ class MailGW: raise MailUsageHelp m = subject_re.match(subject) + + # check for well-formed subject line + if m: + # get the classname + classname = m.group('classname') + if classname is None: + # no classname, fallback on the default + if hasattr(self.instance, 'MAIL_DEFAULT_CLASS') and \ + self.instance.MAIL_DEFAULT_CLASS: + classname = self.instance.MAIL_DEFAULT_CLASS + else: + # fail + m = None + if not m: raise MailUsageError, ''' The message you sent to roundup did not contain a properly formed subject @@ -307,8 +321,7 @@ line. The subject must contain a class name or designator to indicate the Subject was: "%s" '''%subject - # get the classname - classname = m.group('classname') + # get the class try: cl = self.db.getclass(classname) except KeyError: @@ -444,11 +457,15 @@ Subject was: "%s" # handle the users # - # Don't create users if ANONYMOUS_REGISTER is denied - if self.instance.ANONYMOUS_REGISTER == 'deny': + # Don't create users if ANONYMOUS_REGISTER_MAIL is denied + # ... fall back on ANONYMOUS_REGISTER if the other doesn't exist + create = 1 + if hasattr(self.instance, 'ANONYMOUS_REGISTER_MAIL'): + if self.instance.ANONYMOUS_REGISTER_MAIL == 'deny': + create = 0 + elif self.instance.ANONYMOUS_REGISTER == 'deny': create = 0 - else: - create = 1 + author = self.db.uidFromAddress(message.getaddrlist('from')[0], create=create) if not author: @@ -476,7 +493,14 @@ Unknown address: %s r = recipient[1].strip().lower() if r == tracker_email or not r: continue - recipients.append(self.db.uidFromAddress(recipient)) + + # look up the recipient - create if necessary (and we're + # allowed to) + recipient = self.db.uidFromAddress(recipient, create) + + # if all's well, add the recipient to the list + if recipient: + recipients.append(recipient) # # handle message-id and in-reply-to @@ -506,8 +530,8 @@ Unknown address: %s # required body parts. # ACTION: Not handleable as the content is encrypted. # multipart/related (rfc 1872, 2112, 2387): - # The Multipart/Related content-type addresses the MIME representation - # of compound objects. + # The Multipart/Related content-type addresses the MIME + # representation of compound objects. # ACTION: Default. If we are lucky there is a text/plain. # TODO: One should use the start part and look for an Alternative # that is text/plain. @@ -580,7 +604,15 @@ not find a text/plain part to use. else: content = self.get_part_data_decoded(message) - summary, content = parseContent(content) + # figure how much we should muck around with the email body + keep_citations = getattr(self.instance, 'EMAIL_KEEP_QUOTED_TEXT', + 'no') == 'yes' + keep_body = getattr(self.instance, 'EMAIL_LEAVE_BODY_UNCHANGED', + 'no') == 'yes' + + # parse the body of the message, stripping out bits as appropriate + summary, content = parseContent(content, keep_citations, + keep_body) # # handle the attachments @@ -618,25 +650,13 @@ not find a text/plain part to use. current_status == resolved_id): props['status'] = chatting_id - # add nosy in arguments to issue's nosy list - if not props.has_key('nosy'): props['nosy'] = [] - n = {} + # update the nosy list + current = {} for nid in cl.get(nodeid, 'nosy'): - n[nid] = 1 - for value in props['nosy']: - if self.db.hasnode('user', value): - nid = value - else: - continue - if n.has_key(nid): continue - n[nid] = 1 - props['nosy'] = n.keys() - # add assignedto to the nosy list - if props.has_key('assignedto'): - assignedto = props['assignedto'] - if assignedto not in props['nosy']: - props['nosy'].append(assignedto) + current[nid] = 1 + self.updateNosy(cl, props, author, recipients, current) + # create the message message_id = self.db.msg.create(author=author, recipients=recipients, date=date.Date('.'), summary=summary, content=content, files=files, messageid=messageid, @@ -691,30 +711,7 @@ There was a problem with the message you sent: props['messages'] = [message_id] # set up (clean) the nosy list - nosy = props.get('nosy', []) - n = {} - for value in nosy: - nid = value - if n.has_key(nid): continue - n[nid] = 1 - props['nosy'] = n.keys() - # add on the recipients of the message - for recipient in recipients: - if not n.has_key(recipient): - props['nosy'].append(recipient) - n[recipient] = 1 - - # add the author to the nosy list - if not n.has_key(author): - props['nosy'].append(author) - n[author] = 1 - - # add assignedto to the nosy list - if properties.has_key('assignedto') and props.has_key('assignedto'): - assignedto = props['assignedto'] - if not n.has_key(assignedto): - props['nosy'].append(assignedto) - n[assignedto] = 1 + self.updateNosy(cl, props, author, recipients) # and attempt to create the new node try: @@ -728,8 +725,60 @@ There was a problem with the message you sent: # commit the new node(s) to the DB self.db.commit() -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*$')): + return nodeid + + def updateNosy(self, cl, props, author, recipients, current=None): + '''Determine what the nosy list should be given: + + props: properties specified on the subject line of the message + author: the sender of the message + recipients: the recipients (to, cc) of the message + current: if the issue already exists, this is the current nosy + list, as a dictionary. + ''' + if current is None: + current = {} + ok = ('new', 'yes') + else: + ok = ('yes',) + + # add nosy in arguments to issue's nosy list + nosy = props.get('nosy', []) + for value in nosy: + if not self.db.hasnode('user', value): + continue + if not current.has_key(value): + current[value] = 1 + + # add the author to the nosy list + if getattr(self.instance, 'ADD_AUTHOR_TO_NOSY', 'new') in ok: + if not current.has_key(author): + current[author] = 1 + + # add on the recipients of the message + if getattr(self.instance, 'ADD_RECIPIENTS_TO_NOSY', 'new') in ok: + for recipient in recipients: + if not current.has_key(recipient): + current[recipient] = 1 + + # add assignedto(s) to the nosy list + if props.has_key('assignedto'): + propdef = cl.getprops() + if isinstance(propdef['assignedto'], hyperdb.Link): + assignedto_ids = [props['assignedto']] + elif isinstance(propdef['assignedto'], hyperdb.Multilink): + assignedto_ids = props['assignedto'] + for assignedto_id in assignedto_ids: + if not current.has_key(assignedto_id): + current[assignedto_id] = 1 + + props['nosy'] = current.keys() + +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*$'), + original_message=re.compile(r'^[>|\s]*-----Original Message-----$')): ''' 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 @@ -761,9 +810,9 @@ def parseContent(content, blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'), if line[0] not in '>|': break else: - # TODO: people who want to keep quoted bits will want the - # next line... - # l.append(section) + # we keep quoted bits if specified in the config + if keep_citations: + l.append(section) continue # keep this section - it has reponse stuff in it if not summary: @@ -777,15 +826,64 @@ def parseContent(content, blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'), # if we don't have our summary yet use the first line of this # section summary = lines[0] - elif signature.match(lines[0]): + elif signature.match(lines[0]) and 2 <= len(lines) <= 10: + # lose any signature + break + elif original_message.match(lines[0]): + # ditch the stupid Outlook quoting of the entire original message break # and add the section to the output l.append(section) - return summary, '\n\n'.join(l) + # we only set content for those who want to delete cruft from the + # message body, otherwise the body is left untouched. + if not keep_body: + content = '\n\n'.join(l) + return summary, content # # $Log: not supported by cvs2svn $ +# Revision 1.72 2002/05/22 01:24:51 richard +# Added note to MIGRATION about new config vars. Also made us more resilient +# for upgraders. Reinstated list header style (oops) +# +# Revision 1.71 2002/05/08 02:40:55 richard +# grr +# +# Revision 1.70 2002/05/06 23:40:07 richard +# hrm +# +# Revision 1.69 2002/05/06 23:37:21 richard +# Tweaking the signature deletion from mail messages. +# Added nuking of the "-----Original Message-----" crap from Outlook. +# +# Revision 1.68 2002/05/02 07:56:34 richard +# . added option to automatically add the authors and recipients of messages +# to the nosy lists with the options ADD_AUTHOR_TO_NOSY (default 'new') and +# ADD_RECIPIENTS_TO_NOSY (default 'new'). These settings emulate the current +# behaviour. Setting them to 'yes' will add the author/recipients to the nosy +# on messages that create issues and followup messages. +# . added missing documentation for a few of the config option values +# +# Revision 1.67 2002/04/23 15:46:49 rochecompaan +# . stripping of the email message body can now be controlled through +# the config variables EMAIL_KEEP_QUOTED_TEST and +# EMAIL_LEAVE_BODY_UNCHANGED. +# +# Revision 1.66 2002/03/14 23:59:24 richard +# . #517734 ] web header customisation is obscure +# +# Revision 1.65 2002/02/15 00:13:38 richard +# . #503204 ] mailgw needs a default class +# - partially done - the setting of additional properties can wait for a +# better configuration system. +# +# Revision 1.64 2002/02/14 23:46:02 richard +# . #516883 ] mail interface + ANONYMOUS_REGISTER +# +# Revision 1.63 2002/02/12 08:08:55 grubert +# . Clean up mail handling, multipart handling. +# # Revision 1.62 2002/02/05 14:15:29 grubert # . respect encodings in non multipart messages. #