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