1 #!/usr/bin/env python 2 3 """ 4 The handler invocation mechanism. 5 6 Copyright (C) 2014, 2015, 2017 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from imiptools.config import settings 23 from imiptools.data import Object, parse_object, get_value 24 25 try: 26 from cStringIO import StringIO 27 except ImportError: 28 from StringIO import StringIO 29 30 IMIP_COUNTER_AS_REQUEST = settings["IMIP_COUNTER_AS_REQUEST"] 31 32 # Permitted iTIP content types. 33 34 itip_content_types = [ 35 "text/calendar", # from RFC 6047 36 "text/x-vcalendar", "application/ics", # other possibilities 37 ] 38 39 def have_itip_part(part): 40 41 "Return whether 'part' provides iTIP content." 42 43 return part.get_content_type() in itip_content_types and \ 44 part.get_param("method") 45 46 def is_returned_message(message): 47 48 """ 49 Return whether 'message' contains a returned message that should not be 50 handled because it may have originated from the current recipient. 51 """ 52 53 for part in message.walk(): 54 if part.get_content_type() == "message/delivery-status": 55 return True 56 return False 57 58 def consistent_methods(itip_method, part_method): 59 60 """ 61 Return whether 'itip_method' (from content) and 'part_method' (from 62 metadata) are consistent. 63 """ 64 65 return itip_method == part_method or \ 66 IMIP_COUNTER_AS_REQUEST and itip_method == "COUNTER" and \ 67 part_method == "REQUEST" 68 69 def parse_itip_part(part): 70 71 """ 72 Parse the given message 'part' and return a dictionary mapping calendar 73 object type names to lists of fragments, along with the method employed by 74 the parsed calendar object. 75 76 If no iTIP content is found, None is returned. 77 """ 78 79 # Decode the data and parse it. 80 81 f = StringIO(part.get_payload(decode=True)) 82 itip = parse_object(f, part.get_content_charset(), "VCALENDAR") 83 84 # Ignore the part if not a calendar object. 85 86 if not itip: 87 return None 88 89 # Require consistency between declared and employed methods. 90 91 itip_method = get_value(itip, "METHOD") 92 method = part.get_param("method") 93 method = method and method.upper() 94 95 if not consistent_methods(itip_method, method): 96 return None 97 98 # Assert the object's method as the definitive one. 99 100 return itip, itip_method 101 102 def handle_itip_part(part, handlers): 103 104 """ 105 Handle the given iTIP 'part' using the given 'handlers' dictionary. 106 107 Return a list of responses, each response being a tuple of the form 108 (outgoing-recipients, message-part). 109 """ 110 111 itip_details = parse_itip_part(part) 112 if not itip_details: 113 return 114 115 itip, method = itip_details 116 117 # Look for different kinds of sections. 118 119 all_results = [] 120 121 for name, items in itip.items(): 122 123 # Get a handler for the given section. 124 125 handler = handlers.get(name) 126 if not handler: 127 continue 128 129 for item in items: 130 131 # Dispatch to a handler and obtain any response. 132 133 handler.set_object(Object({name : item})) 134 handler.set_identity(method) 135 136 if handler.is_usable(method): 137 138 # Perform the method in a critical section. 139 140 handler.acquire_lock() 141 try: 142 methods[method](handler)() 143 finally: 144 handler.release_lock() 145 146 # Handler registry. 147 148 methods = { 149 "ADD" : lambda handler: handler.add, 150 "CANCEL" : lambda handler: handler.cancel, 151 "COUNTER" : lambda handler: handler.counter, 152 "DECLINECOUNTER" : lambda handler: handler.declinecounter, 153 "PUBLISH" : lambda handler: handler.publish, 154 "REFRESH" : lambda handler: handler.refresh, 155 "REPLY" : lambda handler: handler.reply, 156 "REQUEST" : lambda handler: handler.request, 157 } 158 159 # vim: tabstop=4 expandtab shiftwidth=4