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 __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
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 """
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
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
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
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
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
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
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
291
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
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
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
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
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
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
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
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
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
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
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
514