X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=roundup%2Fmailgw.py;h=d2890e0c196bbb4ac24051becc0cc0b311d7bcda;hb=c28424f9add2dfbcb11b5288c4d5f8e6d99e1d8b;hp=1664484c12081791864a7cf05eea35c6e9eb6118;hpb=4d9945dfdc1630579b99ee992c7887600ddb7f3e;p=roundup.git diff --git a/roundup/mailgw.py b/roundup/mailgw.py index 1664484..d2890e0 100644 --- a/roundup/mailgw.py +++ b/roundup/mailgw.py @@ -247,6 +247,22 @@ class Message(mimetools.Message): parts.append(part) return parts + def _decode_header_to_utf8(self, hdr): + l = [] + prev_encoded = False + for part, encoding in decode_header(hdr): + if encoding: + part = part.decode(encoding) + # RFC 2047 specifies that between encoded parts spaces are + # swallowed while at the borders from encoded to non-encoded + # or vice-versa we must preserve a space. Multiple adjacent + # non-encoded parts should not occur. + if l and prev_encoded != bool(encoding): + l.append(' ') + prev_encoded = bool(encoding) + l.append(part) + return ''.join([s.encode('utf-8') for s in l]) + def getheader(self, name, default=None): hdr = mimetools.Message.getheader(self, name, default) # TODO are there any other False values possible? @@ -257,24 +273,13 @@ class Message(mimetools.Message): return '' if hdr: hdr = hdr.replace('\n','') # Inserted by rfc822.readheaders - # historically this method has returned utf-8 encoded string - l = [] - for part, encoding in decode_header(hdr): - if encoding: - part = part.decode(encoding) - l.append(part) - return ''.join([s.encode('utf-8') for s in l]) + return self._decode_header_to_utf8(hdr) def getaddrlist(self, name): # overload to decode the name part of the address l = [] for (name, addr) in mimetools.Message.getaddrlist(self, name): - p = [] - for part, encoding in decode_header(name): - if encoding: - part = part.decode(encoding) - p.append(part) - name = ''.join([s.encode('utf-8') for s in p]) + name = self._decode_header_to_utf8(name) l.append((name, addr)) return l @@ -549,6 +554,7 @@ class parsedMessage: self.nodeid = None self.author = None self.recipients = None + self.msg_props = {} self.props = None self.content = None self.attachments = None @@ -856,7 +862,7 @@ Unknown address: %(from_address)s 'You are not permitted to access this tracker.') self.author = author - def check_node_permissions(self): + def check_permissions(self): ''' Check if the author has permission to edit or create this class of node ''' @@ -1052,13 +1058,14 @@ encrypted.""") 'You are not permitted to add files to %(classname)s.' ) % self.__dict__ + self.msg_props['files'] = files if self.nodeid: # extend the existing files list fileprop = self.cl.get(self.nodeid, 'files') fileprop.extend(files) files = fileprop - self.props['files'] = files + self.props['files'] = files def create_msg(self): ''' Create msg containing all the relevant information from the message @@ -1066,6 +1073,7 @@ encrypted.""") if not self.properties.has_key('messages'): return msg_props = self.mailgw.get_class_arguments('msg') + self.msg_props.update (msg_props) # Get the message ids inreplyto = self.message.getheader('in-reply-to') or '' @@ -1093,8 +1101,8 @@ not find a text/plain part to use. try: message_id = self.db.msg.create(author=self.author, recipients=self.recipients, date=date.Date('.'), - summary=summary, content=content, files=self.props['files'], - messageid=messageid, inreplyto=inreplyto, **msg_props) + summary=summary, content=content, + messageid=messageid, inreplyto=inreplyto, **self.msg_props) except exceptions.Reject, error: raise MailUsageError, _(""" Mail message was rejected by a detector. @@ -1147,6 +1155,71 @@ There was a problem with the message you sent: return self.nodeid + # XXX Don't enable. This doesn't work yet. +# "[^A-z.]tracker\+(?P[^\d\s]+)(?P\d+)\@some.dom.ain[^A-z.]" + # handle delivery to addresses like:tracker+issue25@some.dom.ain + # use the embedded issue number as our issue +# issue_re = config['MAILGW_ISSUE_ADDRESS_RE'] +# if issue_re: +# for header in ['to', 'cc', 'bcc']: +# addresses = message.getheader(header, '') +# if addresses: +# # FIXME, this only finds the first match in the addresses. +# issue = re.search(issue_re, addresses, 'i') +# if issue: +# classname = issue.group('classname') +# nodeid = issue.group('nodeid') +# break + + # Default sequence of methods to be called on message. Use this for + # easier override of the default message processing + # list consists of tuples (method, return_if_true), the parsing + # returns if the return_if_true flag is set for a method *and* the + # method returns something that evaluates to True. + method_list = [ + # Filter out messages to ignore + (handle_ignore, False), + # Check for usage/help requests + (handle_help, False), + # Check if the subject line is valid + (check_subject, False), + # get importants parts from subject + (parse_subject, False), + # check for registration OTK + (rego_confirm, True), + # get the classname + (get_classname, False), + # get the optional nodeid: + (get_nodeid, False), + # Determine who the author is: + (get_author_id, False), + # allowed to edit or create this class? + (check_permissions, False), + # author may have been created: + # commit author to database and re-open as author + (commit_and_reopen_as_author, False), + # Get the recipients list + (get_recipients, False), + # get the new/updated node props + (get_props, False), + # Handle PGP signed or encrypted messages + (get_pgp_message, False), + # extract content and attachments from message body: + (get_content_and_attachments, False), + # put attachments into files linked to the issue: + (create_files, False), + # create the message if there's a message body (content): + (create_msg, False), + ] + + + def parse (self): + for method, flag in self.method_list: + ret = method(self) + if flag and ret: + return + # perform the node change / create: + return self.create_node() class MailGW: @@ -1362,6 +1435,7 @@ class MailGW: # in some rare cases, a particularly stuffed-up e-mail will make # its way into here... try to handle it gracefully + self.parsed_message = None sendto = message.getaddrlist('resent-from') if not sendto: sendto = message.getaddrlist('from') @@ -1451,77 +1525,8 @@ class MailGW: The following code expects an opened database and a try/finally that closes the database. ''' - parsed_message = self.parsed_message_class(self, message) - - # Filter out messages to ignore - parsed_message.handle_ignore() - - # Check for usage/help requests - parsed_message.handle_help() - - # Check if the subject line is valid - parsed_message.check_subject() - - # XXX Don't enable. This doesn't work yet. - # XXX once this works it should be moved to parsedMessage class -# "[^A-z.]tracker\+(?P[^\d\s]+)(?P\d+)\@some.dom.ain[^A-z.]" - # handle delivery to addresses like:tracker+issue25@some.dom.ain - # use the embedded issue number as our issue -# issue_re = config['MAILGW_ISSUE_ADDRESS_RE'] -# if issue_re: -# for header in ['to', 'cc', 'bcc']: -# addresses = message.getheader(header, '') -# if addresses: -# # FIXME, this only finds the first match in the addresses. -# issue = re.search(issue_re, addresses, 'i') -# if issue: -# classname = issue.group('classname') -# nodeid = issue.group('nodeid') -# break - - # Parse the subject line to get the importants parts - parsed_message.parse_subject() - - # check for registration OTK - if parsed_message.rego_confirm(): - return - - # get the classname - parsed_message.get_classname() - - # get the optional nodeid - parsed_message.get_nodeid() - - # Determine who the author is - parsed_message.get_author_id() - - # make sure they're allowed to edit or create this class - parsed_message.check_node_permissions() - - # author may have been created: - # commit author to database and re-open as author - parsed_message.commit_and_reopen_as_author() - - # Get the recipients list - parsed_message.get_recipients() - - # get the new/updated node props - parsed_message.get_props() - - # Handle PGP signed or encrypted messages - parsed_message.get_pgp_message() - - # extract content and attachments from message body - parsed_message.get_content_and_attachments() - - # put attachments into files linked to the issue - parsed_message.create_files() - - # create the message if there's a message body (content) - parsed_message.create_msg() - - # perform the node change / create - nodeid = parsed_message.create_node() + self.parsed_message = self.parsed_message_class(self, message) + nodeid = self.parsed_message.parse () # commit the changes to the DB self.db.commit() @@ -1658,7 +1663,17 @@ def uidFromAddress(db, address, create=1, **user_props): props = db.user.getprops() if props.has_key('alternate_addresses'): users = db.user.filter(None, {'alternate_addresses': address}) - user = extractUserFromList(db.user, users) + # We want an exact match of the email, not just a substring + # match. Otherwise e.g. support@example.com would match + # discuss-support@example.com which is not what we want. + found_users = [] + for u in users: + alt = db.user.get(u, 'alternate_addresses').split('\n') + for a in alt: + if a.strip().lower() == address.lower(): + found_users.append(u) + break + user = extractUserFromList(db.user, found_users) if user is not None: return user