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