# HG changeset patch # User Paul Boddie # Date 1506963682 -7200 # Node ID efac6e749b7e5d1ebc56756d74fe989e65cbc2fe # Parent 4af5f81f530108c99b05f5b84fa8fab13d24dbe0 Moved various iTIP utility functions into the content module. diff -r 4af5f81f5301 -r efac6e749b7e imiptools/__init__.py --- a/imiptools/__init__.py Sun Oct 01 23:09:50 2017 +0200 +++ b/imiptools/__init__.py Mon Oct 02 19:01:22 2017 +0200 @@ -22,7 +22,8 @@ from email import message_from_file from imiptools.config import settings from imiptools.client import Client -from imiptools.content import handle_itip_part +from imiptools.content import handle_itip_part, have_itip_part, \ + is_returned_message from imiptools.data import get_address, get_addresses, get_uri from imiptools.mail import Messenger from imiptools.stores import get_store, get_publisher, get_journal @@ -32,13 +33,6 @@ EX_TEMPFAIL = 75 -# Permitted iTIP content types. - -itip_content_types = [ - "text/calendar", # from RFC 6047 - "text/x-vcalendar", "application/ics", # other possibilities - ] - # Processing of incoming messages. def get_all_values(msg, key): @@ -363,23 +357,17 @@ # Check for participating recipients. Non-participating recipients will # have their messages left as being unhandled. - if self.outgoing_only or self.is_participating(): + if not is_returned_message(msg) and (self.outgoing_only or self.is_participating()): - # Check for returned messages. + # Handle parts. for part in msg.walk(): - if part.get_content_type() == "message/delivery-status": - break - else: - for part in msg.walk(): - if part.get_content_type() in itip_content_types and \ - part.get_param("method"): + if have_itip_part(part): + if self.debug: + print >>sys.stderr, "Handle method %s..." % part.get_param("method") - if self.debug: - print >>sys.stderr, "Handle method %s..." % part.get_param("method") - - handle_itip_part(part, handlers) - handled = True + handle_itip_part(part, handlers) + handled = True # When processing outgoing messages, no replies or deliveries are # performed. diff -r 4af5f81f5301 -r efac6e749b7e imiptools/content.py --- a/imiptools/content.py Sun Oct 01 23:09:50 2017 +0200 +++ b/imiptools/content.py Mon Oct 02 19:01:22 2017 +0200 @@ -29,6 +29,76 @@ IMIP_COUNTER_AS_REQUEST = settings["IMIP_COUNTER_AS_REQUEST"] +# Permitted iTIP content types. + +itip_content_types = [ + "text/calendar", # from RFC 6047 + "text/x-vcalendar", "application/ics", # other possibilities + ] + +def have_itip_part(part): + + "Return whether 'part' provides iTIP content." + + return part.get_content_type() in itip_content_types and \ + part.get_param("method") + +def is_returned_message(message): + + """ + Return whether 'message' contains a returned message that should not be + handled because it may have originated from the current recipient. + """ + + for part in message.walk(): + if part.get_content_type() == "message/delivery-status": + return True + return False + +def consistent_methods(itip_method, part_method): + + """ + Return whether 'itip_method' (from content) and 'part_method' (from + metadata) are consistent. + """ + + return itip_method == part_method or \ + IMIP_COUNTER_AS_REQUEST and itip_method == "COUNTER" and \ + part_method == "REQUEST" + +def parse_itip_part(part): + + """ + Parse the given message 'part' and return a dictionary mapping calendar + object type names to lists of fragments, along with the method employed by + the parsed calendar object. + + If no iTIP content is found, None is returned. + """ + + # Decode the data and parse it. + + f = StringIO(part.get_payload(decode=True)) + itip = parse_object(f, part.get_content_charset(), "VCALENDAR") + + # Ignore the part if not a calendar object. + + if not itip: + return None + + # Require consistency between declared and employed methods. + + itip_method = get_value(itip, "METHOD") + method = part.get_param("method") + method = method and method.upper() + + if not consistent_methods(itip_method, method): + return None + + # Assert the object's method as the definitive one. + + return itip, itip_method + def handle_itip_part(part, handlers): """ @@ -38,59 +108,40 @@ (outgoing-recipients, message-part). """ - method = part.get_param("method") - method = method and method.upper() - - # Decode the data and parse it. - - f = StringIO(part.get_payload(decode=True)) - - itip = parse_object(f, part.get_content_charset(), "VCALENDAR") - - # Ignore the part if not a calendar object. - - if not itip: + itip_details = parse_itip_part(part) + if not itip_details: return - # Require consistency between declared and employed methods. - - itip_method = get_value(itip, "METHOD") + itip, method = itip_details - if itip_method == method or \ - IMIP_COUNTER_AS_REQUEST and itip_method == "COUNTER" and method == "REQUEST": + # Look for different kinds of sections. - # Assert the object's method as the definitive one. + all_results = [] - method = itip_method + for name, items in itip.items(): - # Look for different kinds of sections. + # Get a handler for the given section. - all_results = [] - - for name, items in itip.items(): - - # Get a handler for the given section. + handler = handlers.get(name) + if not handler: + continue - handler = handlers.get(name) - if not handler: - continue + for item in items: - for item in items: + # Dispatch to a handler and obtain any response. - # Dispatch to a handler and obtain any response. + handler.set_object(Object({name : item})) + handler.set_identity(method) - handler.set_object(Object({name : item})) - handler.set_identity(method) + if handler.is_usable(method): - if handler.is_usable(method): + # Perform the method in a critical section. - # Perform the method in a critical section. - - handler.acquire_lock() - try: - methods[method](handler)() - finally: - handler.release_lock() + handler.acquire_lock() + try: + methods[method](handler)() + finally: + handler.release_lock() # Handler registry.