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