1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 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 668 2007-01-05 16:24:08Z 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   
 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      """ 
 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   
 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   
 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              if self._iq_response_handlers.has_key((sid,ufr)): 
111                  key=(sid,ufr) 
112              elif ( (fr==self.peer or fr==self.me) 
113                      and self._iq_response_handlers.has_key((sid,None))): 
114                  key=(sid,None) 
115              else: 
116                  return False 
117              res_handler, err_handler = self._iq_response_handlers[key] 
118              if stanza.get_type()=="result": 
119                  response = res_handler(stanza) 
120              else: 
121                  response = err_handler(stanza) 
122              del self._iq_response_handlers[key] 
123              self.process_response(response) 
124              return True 
125   
126          q=stanza.get_query() 
127          if not q: 
128              raise BadRequestProtocolError, "Stanza with no child element" 
129          el=q.name 
130          ns=q.ns().getContent() 
131   
132          if typ=="get": 
133              if self._iq_get_handlers.has_key((el,ns)): 
134                  response = self._iq_get_handlers[(el,ns)](stanza) 
135                  self.process_response(response) 
136                  return True 
137              else: 
138                  raise FeatureNotImplementedProtocolError, "Not implemented" 
139          elif typ=="set": 
140              if self._iq_set_handlers.has_key((el,ns)): 
141                  response = self._iq_set_handlers[(el,ns)](stanza) 
142                  self.process_response(response) 
143                  return True 
144              else: 
145                  raise FeatureNotImplementedProtocolError, "Not implemented" 
146          else: 
147              raise BadRequestProtocolError, "Unknown IQ stanza type" 
 148   
150          """ Search the handler list for handlers matching 
151          given stanza type and payload namespace. Run the 
152          handlers found ordering them by priority until 
153          the first one which returns `True`. 
154   
155          :Parameters: 
156              - `handler_list`: list of available handlers 
157              - `typ`: stanza type (value of its "type" attribute) 
158              - `stanza`: the stanza to handle 
159   
160          :return: result of the last handler or `False` if no 
161              handler was found.""" 
162          namespaces=[] 
163          if stanza.xmlnode.children: 
164              c=stanza.xmlnode.children 
165              while c: 
166                  try: 
167                      ns=c.ns() 
168                  except libxml2.treeError: 
169                      ns=None 
170                  if ns is None: 
171                      c=c.next 
172                      continue 
173                  ns_uri=ns.getContent() 
174                  if ns_uri not in namespaces: 
175                      namespaces.append(ns_uri) 
176                  c=c.next 
177          for handler_entry in handler_list: 
178              t=handler_entry[1] 
179              ns=handler_entry[2] 
180              handler=handler_entry[3] 
181              if t!=typ: 
182                  continue 
183              if ns is not None and ns not in namespaces: 
184                  continue 
185              response = handler(stanza) 
186              if self.process_response(response): 
187                  return True 
188          return False 
 189   
191          """Process message stanza. 
192   
193          Pass it to a handler of the stanza's type and payload namespace. 
194          If no handler for the actual stanza type succeeds then hadlers 
195          for type "normal" are used. 
196   
197          :Parameters: 
198              - `stanza`: message stanza to be handled 
199          """ 
200   
201          if not self.initiator and not self.peer_authenticated: 
202              self.__logger.debug("Ignoring message - peer not authenticated yet") 
203              return True 
204   
205          typ=stanza.get_type() 
206          if self.__try_handlers(self._message_handlers,typ,stanza): 
207              return True 
208          if typ!="error": 
209              return self.__try_handlers(self._message_handlers,"normal",stanza) 
210          return False 
 211   
213          """Process presence stanza. 
214   
215          Pass it to a handler of the stanza's type and payload namespace. 
216   
217          :Parameters: 
218              - `stanza`: presence stanza to be handled 
219          """ 
220   
221          if not self.initiator and not self.peer_authenticated: 
222              self.__logger.debug("Ignoring presence - peer not authenticated yet") 
223              return True 
224   
225          typ=stanza.get_type() 
226          if not typ: 
227              typ="available" 
228          return self.__try_handlers(self._presence_handlers,typ,stanza) 
 229   
231          """Process stanza not addressed to us. 
232   
233          Return "recipient-unavailable" return if it is not 
234          "error" nor "result" stanza. 
235   
236          This method should be overriden in derived classes if they 
237          are supposed to handle stanzas not addressed directly to local 
238          stream endpoint. 
239   
240          :Parameters: 
241              - `stanza`: presence stanza to be processed 
242          """ 
243          if stanza.get_type() not in ("error","result"): 
244              r = stanza.make_error_response("recipient-unavailable") 
245              self.send(r) 
246          return True 
 247   
290   
292          """Check "to" attribute of received stream header. 
293   
294          :return: `to` if it is equal to `self.me`, None otherwise. 
295   
296          Should be overriden in derived classes which require other logic 
297          for handling that attribute.""" 
298          if to!=self.me: 
299              return None 
300          return to 
 301   
303          """Set response handler for an IQ "get" or "set" stanza. 
304   
305          This should be called before the stanza is sent. 
306   
307          :Parameters: 
308              - `iq`: an IQ stanza 
309              - `res_handler`: result handler for the stanza. Will be called 
310                when matching <iq type="result"/> is received. Its only 
311                argument will be the stanza received. The handler may return 
312                a stanza or list of stanzas which should be sent in response. 
313              - `err_handler`: error handler for the stanza. Will be called 
314                when matching <iq type="error"/> is received. Its only 
315                argument will be the stanza received. The handler may return 
316                a stanza or list of stanzas which should be sent in response 
317                but this feature should rather not be used (it is better not to 
318                respond to 'error' stanzas). 
319              - `timeout_handler`: timeout handler for the stanza. Will be called 
320                when no matching <iq type="result"/> or <iq type="error"/> is 
321                received in next `timeout` seconds. The handler should accept 
322                two arguments and ignore them. 
323              - `timeout`: timeout value for the stanza. After that time if no 
324                matching <iq type="result"/> nor <iq type="error"/> stanza is 
325                received, then timeout_handler (if given) will be called. 
326          """ 
327          self.lock.acquire() 
328          try: 
329              self._set_response_handlers(iq,res_handler,err_handler,timeout_handler,timeout) 
330          finally: 
331              self.lock.release() 
 332   
334          """Same as `Stream.set_response_handlers` but assume `self.lock` is acquired.""" 
335          self.fix_out_stanza(iq) 
336          to=iq.get_to() 
337          if to: 
338              to=to.as_unicode() 
339          if timeout_handler: 
340              self._iq_response_handlers.set_item((iq.get_id(),to), 
341                      (res_handler,err_handler), 
342                      timeout,timeout_handler) 
343          else: 
344              self._iq_response_handlers.set_item((iq.get_id(),to), 
345                      (res_handler,err_handler),timeout) 
 346   
348          """Set <iq type="get"/> handler. 
349   
350          :Parameters: 
351              - `element`: payload element name 
352              - `namespace`: payload element namespace URI 
353              - `handler`: function to be called when a stanza 
354                with defined element is received. Its only argument 
355                will be the stanza received. The handler may return a stanza or 
356                list of stanzas which should be sent in response. 
357   
358          Only one handler may be defined per one namespaced element. 
359          If a handler for the element was already set it will be lost 
360          after calling this method. 
361          """ 
362          self.lock.acquire() 
363          try: 
364              self._iq_get_handlers[(element,namespace)]=handler 
365          finally: 
366              self.lock.release() 
 367   
369          """Remove <iq type="get"/> handler. 
370   
371          :Parameters: 
372              - `element`: payload element name 
373              - `namespace`: payload element namespace URI 
374          """ 
375          self.lock.acquire() 
376          try: 
377              if self._iq_get_handlers.has_key((element,namespace)): 
378                  del self._iq_get_handlers[(element,namespace)] 
379          finally: 
380              self.lock.release() 
 381   
383          """Set <iq type="set"/> handler. 
384   
385          :Parameters: 
386              - `element`: payload element name 
387              - `namespace`: payload element namespace URI 
388              - `handler`: function to be called when a stanza 
389                with defined element is received. Its only argument 
390                will be the stanza received. The handler may return a stanza or 
391                list of stanzas which should be sent in response. 
392   
393   
394          Only one handler may be defined per one namespaced element. 
395          If a handler for the element was already set it will be lost 
396          after calling this method.""" 
397          self.lock.acquire() 
398          try: 
399              self._iq_set_handlers[(element,namespace)]=handler 
400          finally: 
401              self.lock.release() 
 402   
404          """Remove <iq type="set"/> handler. 
405   
406          :Parameters: 
407              - `element`: payload element name. 
408              - `namespace`: payload element namespace URI.""" 
409          self.lock.acquire() 
410          try: 
411              if self._iq_set_handlers.has_key((element,namespace)): 
412                  del self._iq_set_handlers[(element,namespace)] 
413          finally: 
414              self.lock.release() 
 415   
416 -    def __add_handler(self,handler_list,typ,namespace,priority,handler): 
 417          """Add a handler function to a prioritized handler list. 
418   
419          :Parameters: 
420              - `handler_list`: a handler list. 
421              - `typ`: stanza type. 
422              - `namespace`: stanza payload namespace. 
423              - `priority`: handler priority. Must be >=0 and <=100. Handlers 
424                with lower priority list will be tried first.""" 
425          if priority<0 or priority>100: 
426              raise ValueError,"Bad handler priority (must be in 0:100)" 
427          handler_list.append((priority,typ,namespace,handler)) 
428          handler_list.sort() 
 429   
431          """Set a handler for <message/> stanzas. 
432   
433          :Parameters: 
434              - `typ`: message type. `None` will be treated the same as "normal", 
435                and will be the default for unknown types (those that have no 
436                handler associated). 
437              - `namespace`: payload namespace. If `None` that message with any 
438                payload (or even with no payload) will match. 
439              - `priority`: priority value for the handler. Handlers with lower 
440                priority value are tried first. 
441              - `handler`: function to be called when a message stanza 
442                with defined type and payload namespace is received. Its only 
443                argument will be the stanza received. The handler may return a 
444                stanza or list of stanzas which should be sent in response. 
445   
446          Multiple <message /> handlers with the same type/namespace/priority may 
447          be set. Order of calling handlers with the same priority is not defined. 
448          Handlers will be called in priority order until one of them returns True or 
449          any stanza(s) to send (even empty list will do). 
450          """ 
451          self.lock.acquire() 
452          try: 
453              if not typ: 
454                  typ=="normal" 
455              self.__add_handler(self._message_handlers,typ,namespace,priority,handler) 
456          finally: 
457              self.lock.release() 
 458   
460          """Set a handler for <presence/> stanzas. 
461   
462          :Parameters: 
463              - `typ`: presence type. "available" will be treated the same as `None`. 
464              - `namespace`: payload namespace. If `None` that presence with any 
465                payload (or even with no payload) will match. 
466              - `priority`: priority value for the handler. Handlers with lower 
467                priority value are tried first. 
468              - `handler`: function to be called when a presence stanza 
469                with defined type and payload namespace is received. Its only 
470                argument will be the stanza received. The handler may return a 
471                stanza or list of stanzas which should be sent in response. 
472   
473          Multiple <presence /> handlers with the same type/namespace/priority may 
474          be set. Order of calling handlers with the same priority is not defined. 
475          Handlers will be called in priority order until one of them returns 
476          True or any stanza(s) to send (even empty list will do). 
477          """ 
478          self.lock.acquire() 
479          try: 
480              if not typ: 
481                  typ="available" 
482              self.__add_handler(self._presence_handlers,typ,namespace,priority,handler) 
483          finally: 
484              self.lock.release() 
 485   
487          """Modify incoming stanza before processing it. 
488   
489          This implementation does nothig. It should be overriden in derived 
490          classes if needed.""" 
491          pass 
 492   
494          """Modify outgoing stanza before sending into the stream. 
495   
496          This implementation does nothig. It should be overriden in derived 
497          classes if needed.""" 
498          pass 
 499   
500   
501 -    def send(self,stanza): 
 502          """Send a stanza somwhere. This one does nothing. Should be overriden 
503          in derived classes. 
504   
505          :Parameters: 
506              - `stanza`: the stanza to send. 
507          :Types: 
508              - `stanza`: `pyxmpp.stanza.Stanza`""" 
509          raise NotImplementedError,"This method must be overriden in derived classes.""" 
  510   
511   
512   
513