pyxmpp.stanzaprocessor

Package pyxmpp :: Module stanzaprocessor |
[hide private]

# Source Code for Module pyxmpp.stanzaprocessor

      1  #
      2  # (C) Copyright 2003-2010 Jacek Konieczny <jajcus@jajcus.net>
      3  #
      4  # This program is free software; you can redistribute it and/or modify
      5  # it under the terms of the GNU Lesser General Public License Version
      6  # 2.1 as published by the Free Software Foundation.
      7  #
      8  # This program is distributed in the hope that it will be useful,
      9  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     11  # GNU Lesser General Public License for more details.
     12  #
     13  # You should have received a copy of the GNU Lesser General Public
     14  # License along with this program; if not, write to the Free Software
     15  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     16  #
     17
     18  """Handling of XMPP stanzas.
     19
     20  Normative reference:
     21    - `RFC 3920 <http://www.ietf.org/rfc/rfc3920.txt>`__
     22  """
     23
     24  __revision__="$Id: stanzaprocessor.py 714 2010-04-05 10:20:10Z jajcus $"
     25  __docformat__="restructuredtext en"
     26
     27  import libxml2
     28  import logging
     29  import threading
     30
     31  from pyxmpp.expdict import ExpiringDictionary
     32  from pyxmpp.exceptions import ProtocolError, BadRequestProtocolError, FeatureNotImplementedProtocolError
     33  from pyxmpp.stanza import Stanza
     34
     35 -class StanzaProcessor:
     36      """Universal stanza handler/router class.
     37
     38      Provides facilities to set up custom handlers for various types of stanzas.
     39
     40      :Ivariables:
     41          - `lock`: lock object used to synchronize access to the
     42            `StanzaProcessor` object.
     43          - `me`: local JID.
     44          - `peer`: remote stream endpoint JID.
     45          - `process_all_stanzas`: when `True` then all stanzas received are
     46            considered local.
     47          - `initiator`: `True` if local stream endpoint is the initiating entity.
     48      """
     49 -    def __init__(self):
     50          """Initialize a `StanzaProcessor` object."""
     51          self.me=None
     52          self.peer=None
     53          self.initiator=None
     54          self.peer_authenticated=False
     55          self.process_all_stanzas=True
     56          self._iq_response_handlers=ExpiringDictionary()
     57          self._iq_get_handlers={}
     58          self._iq_set_handlers={}
     59          self._message_handlers=[]
     60          self._presence_handlers=[]
     61          self.__logger=logging.getLogger("pyxmpp.Stream")
     62          self.lock=threading.RLock()
     63
     64 -    def process_response(self, response):
     65          """Examines out the response returned by a stanza handler and sends all
     66          stanzas provided.
     67
     68          :Returns:
     69             - `True`: if `response` is `Stanza`, iterable or `True` (meaning the stanza was processed).
     70             - `False`: when `response` is `False` or `None`
     71          :returntype: `bool`
     72          """
     73
     74          if response is None or response is False:
     75              return False
     76
     77          if isinstance(response, Stanza):
     78              self.send(response)
     79              return True
     80
     81          try:
     82              response = iter(response)
     83          except TypeError:
     84              return bool(response)
     85
     86          for stanza in response:
     87              if isinstance(stanza, Stanza):
     88                  self.send(stanza)
     89          return True
     90
     91 -    def process_iq(self, stanza):
     92          """Process IQ stanza received.
     93
     94          :Parameters:
     95              - `stanza`: the stanza received
     96
     97          If a matching handler is available pass the stanza to it.
     98          Otherwise ignore it if it is "error" or "result" stanza
     99          or return "feature-not-implemented" error."""
    100
    101          sid=stanza.get_id()
    102          fr=stanza.get_from()
    103
    104          typ=stanza.get_type()
    105          if typ in ("result","error"):
    106              if fr:
    107                  ufr=fr.as_unicode()
    108              else:
    109                  ufr=None
    110              res_handler = err_handler = None
    111              try:
    112                  res_handler, err_handler = self._iq_response_handlers.pop((sid,ufr))
    113              except KeyError:
    114                  if ( (fr==self.peer or fr==self.me or fr==self.me.bare()) ):
    115                      try:
    116                          res_handler, err_handler = self._iq_response_handlers.pop((sid,None))
    117                      except KeyError:
    118                          pass
    119                  if None is res_handler is err_handler:
    120                      return False
    121              if typ=="result":
    122                  response = res_handler(stanza)
    123              else:
    124                  response = err_handler(stanza)
    125              self.process_response(response)
    126              return True
    127
    128          q=stanza.get_query()
    129          if not q:
    130              raise BadRequestProtocolError, "Stanza with no child element"
    131          el=q.name
    132          ns=q.ns().getContent()
    133
    134          if typ=="get":
    135              if self._iq_get_handlers.has_key((el,ns)):
    136                  response = self._iq_get_handlers[(el,ns)](stanza)
    137                  self.process_response(response)
    138                  return True
    139              else:
    140                  raise FeatureNotImplementedProtocolError, "Not implemented"
    141          elif typ=="set":
    142              if self._iq_set_handlers.has_key((el,ns)):
    143                  response = self._iq_set_handlers[(el,ns)](stanza)
    144                  self.process_response(response)
    145                  return True
    146              else:
    147                  raise FeatureNotImplementedProtocolError, "Not implemented"
    148          else:
    149              raise BadRequestProtocolError, "Unknown IQ stanza type"
    150
    151 -    def __try_handlers(self,handler_list,typ,stanza):
    152          """ Search the handler list for handlers matching
    153          given stanza type and payload namespace. Run the
    154          handlers found ordering them by priority until
    155          the first one which returns `True`.
    156
    157          :Parameters:
    158              - `handler_list`: list of available handlers
    159              - `typ`: stanza type (value of its "type" attribute)
    160              - `stanza`: the stanza to handle
    161
    162          :return: result of the last handler or `False` if no
    163              handler was found."""
    164          namespaces=[]
    165          if stanza.xmlnode.children:
    166              c=stanza.xmlnode.children
    167              while c:
    168                  try:
    169                      ns=c.ns()
    170                  except libxml2.treeError:
    171                      ns=None
    172                  if ns is None:
    173                      c=c.next
    174                      continue
    175                  ns_uri=ns.getContent()
    176                  if ns_uri not in namespaces:
    177                      namespaces.append(ns_uri)
    178                  c=c.next
    179          for handler_entry in handler_list:
    180              t=handler_entry[1]
    181              ns=handler_entry[2]
    182              handler=handler_entry[3]
    183              if t!=typ:
    184                  continue
    185              if ns is not None and ns not in namespaces:
    186                  continue
    187              response = handler(stanza)
    188              if self.process_response(response):
    189                  return True
    190          return False
    191
    192 -    def process_message(self,stanza):
    193          """Process message stanza.
    194
    195          Pass it to a handler of the stanza's type and payload namespace.
    196          If no handler for the actual stanza type succeeds then hadlers
    197          for type "normal" are used.
    198
    199          :Parameters:
    200              - `stanza`: message stanza to be handled
    201          """
    202
    203          if not self.initiator and not self.peer_authenticated:
    204              self.__logger.debug("Ignoring message - peer not authenticated yet")
    205              return True
    206
    207          typ=stanza.get_type()
    208          if self.__try_handlers(self._message_handlers,typ,stanza):
    209              return True
    210          if typ!="error":
    211              return self.__try_handlers(self._message_handlers,"normal",stanza)
    212          return False
    213
    214 -    def process_presence(self,stanza):
    215          """Process presence stanza.
    216
    217          Pass it to a handler of the stanza's type and payload namespace.
    218
    219          :Parameters:
    220              - `stanza`: presence stanza to be handled
    221          """
    222
    223          if not self.initiator and not self.peer_authenticated:
    224              self.__logger.debug("Ignoring presence - peer not authenticated yet")
    225              return True
    226
    227          typ=stanza.get_type()
    228          if not typ:
    229              typ="available"
    230          return self.__try_handlers(self._presence_handlers,typ,stanza)
    231
    232 -    def route_stanza(self,stanza):
    233          """Process stanza not addressed to us.
    234
    235          Return "recipient-unavailable" return if it is not
    236          "error" nor "result" stanza.
    237
    238          This method should be overriden in derived classes if they
    239          are supposed to handle stanzas not addressed directly to local
    240          stream endpoint.
    241
    242          :Parameters:
    243              - `stanza`: presence stanza to be processed
    244          """
    245          if stanza.get_type() not in ("error","result"):
    246              r = stanza.make_error_response("recipient-unavailable")
    247              self.send(r)
    248          return True
    249
    250 -    def process_stanza(self,stanza):
    251          """Process stanza received from the stream.
    252
    253          First "fix" the stanza with `self.fix_in_stanza()`,
    254          then pass it to `self.route_stanza()` if it is not directed
    255          to `self.me` and `self.process_all_stanzas` is not True. Otherwise
    256          stanza is passwd to `self.process_iq()`, `self.process_message()`
    257          or `self.process_presence()` appropriately.
    258
    259          :Parameters:
    260              - `stanza`: the stanza received.
    261
    262          :returns: `True` when stanza was handled
    263          """
    264
    265          self.fix_in_stanza(stanza)
    266          to=stanza.get_to()
    267
    268          if not self.process_all_stanzas and to and to!=self.me and to.bare()!=self.me.bare():
    269              return self.route_stanza(stanza)
    270
    271          try:
    272              if stanza.stanza_type=="iq":
    273                  if self.process_iq(stanza):
    274                      return True
    275              elif stanza.stanza_type=="message":
    276                  if self.process_message(stanza):
    277                      return True
    278              elif stanza.stanza_type=="presence":
    279                  if self.process_presence(stanza):
    280                      return True
    281          except ProtocolError, e:
    282              typ = stanza.get_type()
    283              if typ != 'error' and (typ != 'result' or stanza.stanza_type != 'iq'):
    284                  r = stanza.make_error_response(e.xmpp_name)
    285                  self.send(r)
    286                  e.log_reported()
    287              else:
    288                  e.log_ignored()
    289
    290          self.__logger.debug("Unhandled %r stanza: %r" % (stanza.stanza_type,stanza.serialize()))
    291          return False
    292
    293 -    def check_to(self,to):
    294          """Check "to" attribute of received stream header.
    295
    296          :return: `to` if it is equal to `self.me`, None otherwise.
    297
    298          Should be overriden in derived classes which require other logic
    299          for handling that attribute."""
    300          if to!=self.me:
    301              return None
    302          return to
    303
    304 -    def set_response_handlers(self,iq,res_handler,err_handler,timeout_handler=None,timeout=300):
    305          """Set response handler for an IQ "get" or "set" stanza.
    306
    307          This should be called before the stanza is sent.
    308
    309          :Parameters:
    310              - `iq`: an IQ stanza
    311              - `res_handler`: result handler for the stanza. Will be called
    312                when matching <iq type="result"/> is received. Its only
    313                argument will be the stanza received. The handler may return
    314                a stanza or list of stanzas which should be sent in response.
    315              - `err_handler`: error handler for the stanza. Will be called
    316                when matching <iq type="error"/> is received. Its only
    317                argument will be the stanza received. The handler may return
    318                a stanza or list of stanzas which should be sent in response
    319                but this feature should rather not be used (it is better not to
    320                respond to 'error' stanzas).
    321              - `timeout_handler`: timeout handler for the stanza. Will be called
    322                when no matching <iq type="result"/> or <iq type="error"/> is
    323                received in next `timeout` seconds. The handler should accept
    324                two arguments and ignore them.
    325              - `timeout`: timeout value for the stanza. After that time if no
    326                matching <iq type="result"/> nor <iq type="error"/> stanza is
    327                received, then timeout_handler (if given) will be called.
    328          """
    329          self.lock.acquire()
    330          try:
    331              self._set_response_handlers(iq,res_handler,err_handler,timeout_handler,timeout)
    332          finally:
    333              self.lock.release()
    334
    335 -    def _set_response_handlers(self,iq,res_handler,err_handler,timeout_handler=None,timeout=300):
    336          """Same as `Stream.set_response_handlers` but assume `self.lock` is acquired."""
    337          self.fix_out_stanza(iq)
    338          to=iq.get_to()
    339          if to:
    340              to=to.as_unicode()
    341          if timeout_handler:
    342              self._iq_response_handlers.set_item((iq.get_id(),to),
    343                      (res_handler,err_handler),
    344                      timeout,timeout_handler)
    345          else:
    346              self._iq_response_handlers.set_item((iq.get_id(),to),
    347                      (res_handler,err_handler),timeout)
    348
    349 -    def set_iq_get_handler(self,element,namespace,handler):
    350          """Set <iq type="get"/> handler.
    351
    352          :Parameters:
    353              - `element`: payload element name
    354              - `namespace`: payload element namespace URI
    355              - `handler`: function to be called when a stanza
    356                with defined element is received. Its only argument
    357                will be the stanza received. The handler may return a stanza or
    358                list of stanzas which should be sent in response.
    359
    360          Only one handler may be defined per one namespaced element.
    361          If a handler for the element was already set it will be lost
    362          after calling this method.
    363          """
    364          self.lock.acquire()
    365          try:
    366              self._iq_get_handlers[(element,namespace)]=handler
    367          finally:
    368              self.lock.release()
    369
    370 -    def unset_iq_get_handler(self,element,namespace):
    371          """Remove <iq type="get"/> handler.
    372
    373          :Parameters:
    374              - `element`: payload element name
    375              - `namespace`: payload element namespace URI
    376          """
    377          self.lock.acquire()
    378          try:
    379              if self._iq_get_handlers.has_key((element,namespace)):
    380                  del self._iq_get_handlers[(element,namespace)]
    381          finally:
    382              self.lock.release()
    383
    384 -    def set_iq_set_handler(self,element,namespace,handler):
    385          """Set <iq type="set"/> handler.
    386
    387          :Parameters:
    388              - `element`: payload element name
    389              - `namespace`: payload element namespace URI
    390              - `handler`: function to be called when a stanza
    391                with defined element is received. Its only argument
    392                will be the stanza received. The handler may return a stanza or
    393                list of stanzas which should be sent in response.
    394
    395
    396          Only one handler may be defined per one namespaced element.
    397          If a handler for the element was already set it will be lost
    398          after calling this method."""
    399          self.lock.acquire()
    400          try:
    401              self._iq_set_handlers[(element,namespace)]=handler
    402          finally:
    403              self.lock.release()
    404
    405 -    def unset_iq_set_handler(self,element,namespace):
    406          """Remove <iq type="set"/> handler.
    407
    408          :Parameters:
    409              - `element`: payload element name.
    410              - `namespace`: payload element namespace URI."""
    411          self.lock.acquire()
    412          try:
    413              if self._iq_set_handlers.has_key((element,namespace)):
    414                  del self._iq_set_handlers[(element,namespace)]
    415          finally:
    416              self.lock.release()
    417
    418 -    def __add_handler(self,handler_list,typ,namespace,priority,handler):
    419          """Add a handler function to a prioritized handler list.
    420
    421          :Parameters:
    422              - `handler_list`: a handler list.
    423              - `typ`: stanza type.
    424              - `namespace`: stanza payload namespace.
    425              - `priority`: handler priority. Must be >=0 and <=100. Handlers
    426                with lower priority list will be tried first."""
    427          if priority<0 or priority>100:
    428              raise ValueError,"Bad handler priority (must be in 0:100)"
    429          handler_list.append((priority,typ,namespace,handler))
    430          handler_list.sort()
    431
    432 -    def set_message_handler(self, typ, handler, namespace=None, priority=100):
    433          """Set a handler for <message/> stanzas.
    434
    435          :Parameters:
    436              - `typ`: message type. `None` will be treated the same as "normal",
    437                and will be the default for unknown types (those that have no
    438                handler associated).
    439              - `namespace`: payload namespace. If `None` that message with any
    440                payload (or even with no payload) will match.
    441              - `priority`: priority value for the handler. Handlers with lower
    442                priority value are tried first.
    443              - `handler`: function to be called when a message stanza
    444                with defined type and payload namespace is received. Its only
    445                argument will be the stanza received. The handler may return a
    446                stanza or list of stanzas which should be sent in response.
    447
    448          Multiple <message /> handlers with the same type/namespace/priority may
    449          be set. Order of calling handlers with the same priority is not defined.
    450          Handlers will be called in priority order until one of them returns True or
    451          any stanza(s) to send (even empty list will do).
    452          """
    453          self.lock.acquire()
    454          try:
    455              if not typ:
    456                  typ=="normal"
    457              self.__add_handler(self._message_handlers,typ,namespace,priority,handler)
    458          finally:
    459              self.lock.release()
    460
    461 -    def set_presence_handler(self,typ,handler,namespace=None,priority=100):
    462          """Set a handler for <presence/> stanzas.
    463
    464          :Parameters:
    465              - `typ`: presence type. "available" will be treated the same as `None`.
    466              - `namespace`: payload namespace. If `None` that presence with any
    467                payload (or even with no payload) will match.
    468              - `priority`: priority value for the handler. Handlers with lower
    469                priority value are tried first.
    470              - `handler`: function to be called when a presence stanza
    471                with defined type and payload namespace is received. Its only
    472                argument will be the stanza received. The handler may return a
    473                stanza or list of stanzas which should be sent in response.
    474
    475          Multiple <presence /> handlers with the same type/namespace/priority may
    476          be set. Order of calling handlers with the same priority is not defined.
    477          Handlers will be called in priority order until one of them returns
    478          True or any stanza(s) to send (even empty list will do).
    479          """
    480          self.lock.acquire()
    481          try:
    482              if not typ:
    483                  typ="available"
    484              self.__add_handler(self._presence_handlers,typ,namespace,priority,handler)
    485          finally:
    486              self.lock.release()
    487
    488 -    def fix_in_stanza(self,stanza):
    489          """Modify incoming stanza before processing it.
    490
    491          This implementation does nothig. It should be overriden in derived
    492          classes if needed."""
    493          pass
    494
    495 -    def fix_out_stanza(self,stanza):
    496          """Modify outgoing stanza before sending into the stream.
    497
    498          This implementation does nothig. It should be overriden in derived
    499          classes if needed."""
    500          pass
    501
    502
    503 -    def send(self,stanza):
    504          """Send a stanza somwhere. This one does nothing. Should be overriden
    505          in derived classes.
    506
    507          :Parameters:
    508              - `stanza`: the stanza to send.
    509          :Types:
    510              - `stanza`: `pyxmpp.stanza.Stanza`"""
    511          raise NotImplementedError,"This method must be overriden in derived classes."""
    512
    513
    514  # vi: sts=4 et sw=4
    515