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 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
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 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
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
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
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
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
292
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
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
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
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
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
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
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
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
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
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
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
515