imip-agent

imiptools/content.py

1286:efac6e749b7e
2017-10-02 Paul Boddie Moved various iTIP utility functions into the content module. client-editing-simplification
     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