1.1 --- a/imiptools/__init__.py Sun Oct 01 23:09:50 2017 +0200
1.2 +++ b/imiptools/__init__.py Mon Oct 02 19:01:22 2017 +0200
1.3 @@ -22,7 +22,8 @@
1.4 from email import message_from_file
1.5 from imiptools.config import settings
1.6 from imiptools.client import Client
1.7 -from imiptools.content import handle_itip_part
1.8 +from imiptools.content import handle_itip_part, have_itip_part, \
1.9 + is_returned_message
1.10 from imiptools.data import get_address, get_addresses, get_uri
1.11 from imiptools.mail import Messenger
1.12 from imiptools.stores import get_store, get_publisher, get_journal
1.13 @@ -32,13 +33,6 @@
1.14
1.15 EX_TEMPFAIL = 75
1.16
1.17 -# Permitted iTIP content types.
1.18 -
1.19 -itip_content_types = [
1.20 - "text/calendar", # from RFC 6047
1.21 - "text/x-vcalendar", "application/ics", # other possibilities
1.22 - ]
1.23 -
1.24 # Processing of incoming messages.
1.25
1.26 def get_all_values(msg, key):
1.27 @@ -363,23 +357,17 @@
1.28 # Check for participating recipients. Non-participating recipients will
1.29 # have their messages left as being unhandled.
1.30
1.31 - if self.outgoing_only or self.is_participating():
1.32 + if not is_returned_message(msg) and (self.outgoing_only or self.is_participating()):
1.33
1.34 - # Check for returned messages.
1.35 + # Handle parts.
1.36
1.37 for part in msg.walk():
1.38 - if part.get_content_type() == "message/delivery-status":
1.39 - break
1.40 - else:
1.41 - for part in msg.walk():
1.42 - if part.get_content_type() in itip_content_types and \
1.43 - part.get_param("method"):
1.44 + if have_itip_part(part):
1.45 + if self.debug:
1.46 + print >>sys.stderr, "Handle method %s..." % part.get_param("method")
1.47
1.48 - if self.debug:
1.49 - print >>sys.stderr, "Handle method %s..." % part.get_param("method")
1.50 -
1.51 - handle_itip_part(part, handlers)
1.52 - handled = True
1.53 + handle_itip_part(part, handlers)
1.54 + handled = True
1.55
1.56 # When processing outgoing messages, no replies or deliveries are
1.57 # performed.
2.1 --- a/imiptools/content.py Sun Oct 01 23:09:50 2017 +0200
2.2 +++ b/imiptools/content.py Mon Oct 02 19:01:22 2017 +0200
2.3 @@ -29,6 +29,76 @@
2.4
2.5 IMIP_COUNTER_AS_REQUEST = settings["IMIP_COUNTER_AS_REQUEST"]
2.6
2.7 +# Permitted iTIP content types.
2.8 +
2.9 +itip_content_types = [
2.10 + "text/calendar", # from RFC 6047
2.11 + "text/x-vcalendar", "application/ics", # other possibilities
2.12 + ]
2.13 +
2.14 +def have_itip_part(part):
2.15 +
2.16 + "Return whether 'part' provides iTIP content."
2.17 +
2.18 + return part.get_content_type() in itip_content_types and \
2.19 + part.get_param("method")
2.20 +
2.21 +def is_returned_message(message):
2.22 +
2.23 + """
2.24 + Return whether 'message' contains a returned message that should not be
2.25 + handled because it may have originated from the current recipient.
2.26 + """
2.27 +
2.28 + for part in message.walk():
2.29 + if part.get_content_type() == "message/delivery-status":
2.30 + return True
2.31 + return False
2.32 +
2.33 +def consistent_methods(itip_method, part_method):
2.34 +
2.35 + """
2.36 + Return whether 'itip_method' (from content) and 'part_method' (from
2.37 + metadata) are consistent.
2.38 + """
2.39 +
2.40 + return itip_method == part_method or \
2.41 + IMIP_COUNTER_AS_REQUEST and itip_method == "COUNTER" and \
2.42 + part_method == "REQUEST"
2.43 +
2.44 +def parse_itip_part(part):
2.45 +
2.46 + """
2.47 + Parse the given message 'part' and return a dictionary mapping calendar
2.48 + object type names to lists of fragments, along with the method employed by
2.49 + the parsed calendar object.
2.50 +
2.51 + If no iTIP content is found, None is returned.
2.52 + """
2.53 +
2.54 + # Decode the data and parse it.
2.55 +
2.56 + f = StringIO(part.get_payload(decode=True))
2.57 + itip = parse_object(f, part.get_content_charset(), "VCALENDAR")
2.58 +
2.59 + # Ignore the part if not a calendar object.
2.60 +
2.61 + if not itip:
2.62 + return None
2.63 +
2.64 + # Require consistency between declared and employed methods.
2.65 +
2.66 + itip_method = get_value(itip, "METHOD")
2.67 + method = part.get_param("method")
2.68 + method = method and method.upper()
2.69 +
2.70 + if not consistent_methods(itip_method, method):
2.71 + return None
2.72 +
2.73 + # Assert the object's method as the definitive one.
2.74 +
2.75 + return itip, itip_method
2.76 +
2.77 def handle_itip_part(part, handlers):
2.78
2.79 """
2.80 @@ -38,59 +108,40 @@
2.81 (outgoing-recipients, message-part).
2.82 """
2.83
2.84 - method = part.get_param("method")
2.85 - method = method and method.upper()
2.86 -
2.87 - # Decode the data and parse it.
2.88 -
2.89 - f = StringIO(part.get_payload(decode=True))
2.90 -
2.91 - itip = parse_object(f, part.get_content_charset(), "VCALENDAR")
2.92 -
2.93 - # Ignore the part if not a calendar object.
2.94 -
2.95 - if not itip:
2.96 + itip_details = parse_itip_part(part)
2.97 + if not itip_details:
2.98 return
2.99
2.100 - # Require consistency between declared and employed methods.
2.101 -
2.102 - itip_method = get_value(itip, "METHOD")
2.103 + itip, method = itip_details
2.104
2.105 - if itip_method == method or \
2.106 - IMIP_COUNTER_AS_REQUEST and itip_method == "COUNTER" and method == "REQUEST":
2.107 + # Look for different kinds of sections.
2.108
2.109 - # Assert the object's method as the definitive one.
2.110 + all_results = []
2.111
2.112 - method = itip_method
2.113 + for name, items in itip.items():
2.114
2.115 - # Look for different kinds of sections.
2.116 + # Get a handler for the given section.
2.117
2.118 - all_results = []
2.119 -
2.120 - for name, items in itip.items():
2.121 -
2.122 - # Get a handler for the given section.
2.123 + handler = handlers.get(name)
2.124 + if not handler:
2.125 + continue
2.126
2.127 - handler = handlers.get(name)
2.128 - if not handler:
2.129 - continue
2.130 + for item in items:
2.131
2.132 - for item in items:
2.133 + # Dispatch to a handler and obtain any response.
2.134
2.135 - # Dispatch to a handler and obtain any response.
2.136 + handler.set_object(Object({name : item}))
2.137 + handler.set_identity(method)
2.138
2.139 - handler.set_object(Object({name : item}))
2.140 - handler.set_identity(method)
2.141 + if handler.is_usable(method):
2.142
2.143 - if handler.is_usable(method):
2.144 + # Perform the method in a critical section.
2.145
2.146 - # Perform the method in a critical section.
2.147 -
2.148 - handler.acquire_lock()
2.149 - try:
2.150 - methods[method](handler)()
2.151 - finally:
2.152 - handler.release_lock()
2.153 + handler.acquire_lock()
2.154 + try:
2.155 + methods[method](handler)()
2.156 + finally:
2.157 + handler.release_lock()
2.158
2.159 # Handler registry.
2.160