Package pyxmpp :: Module stanzaprocessor
[hide private]

Source Code for Module pyxmpp.stanzaprocessor

  1  # 
  2  # (C) Copyright 2003-2004 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 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   
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 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
149 - def __try_handlers(self,handler_list,typ,stanza):
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
190 - def process_message(self,stanza):
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
212 - def process_presence(self,stanza):
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
230 - def route_stanza(self,stanza):
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
248 - def process_stanza(self,stanza):
249 """Process stanza received from the stream. 250 251 First "fix" the stanza with `self.fix_in_stanza()`, 252 then pass it to `self.route_stanza()` if it is not directed 253 to `self.me` and `self.process_all_stanzas` is not True. Otherwise 254 stanza is passwd to `self.process_iq()`, `self.process_message()` 255 or `self.process_presence()` appropriately. 256 257 :Parameters: 258 - `stanza`: the stanza received. 259 260 :returns: `True` when stanza was handled 261 """ 262 263 self.fix_in_stanza(stanza) 264 to=stanza.get_to() 265 266 if not self.process_all_stanzas and to and to!=self.me and to.bare()!=self.me.bare(): 267 return self.route_stanza(stanza) 268 269 try: 270 if stanza.stanza_type=="iq": 271 if self.process_iq(stanza): 272 return True 273 elif stanza.stanza_type=="message": 274 if self.process_message(stanza): 275 return True 276 elif stanza.stanza_type=="presence": 277 if self.process_presence(stanza): 278 return True 279 except ProtocolError, e: 280 typ = stanza.get_type() 281 if typ != 'error' and (typ != 'result' or stanza.stanza_type != 'iq'): 282 r = stanza.make_error_response(e.xmpp_name) 283 self.send(r) 284 e.log_reported() 285 else: 286 e.log_ignored() 287 288 self.__logger.debug("Unhandled %r stanza: %r" % (stanza.stanza_type,stanza.serialize())) 289 return False
290
291 - def check_to(self,to):
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
302 - def set_response_handlers(self,iq,res_handler,err_handler,timeout_handler=None,timeout=300):
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
333 - def _set_response_handlers(self,iq,res_handler,err_handler,timeout_handler=None,timeout=300):
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
347 - def set_iq_get_handler(self,element,namespace,handler):
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
368 - def unset_iq_get_handler(self,element,namespace):
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
382 - def set_iq_set_handler(self,element,namespace,handler):
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
403 - def unset_iq_set_handler(self,element,namespace):
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
430 - def set_message_handler(self, typ, handler, namespace=None, priority=100):
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
459 - def set_presence_handler(self,typ,handler,namespace=None,priority=100):
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
486 - def fix_in_stanza(self,stanza):
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
493 - def fix_out_stanza(self,stanza):
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 # vi: sts=4 et sw=4 513