iq.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. """
  2. SleekXMPP: The Sleek XMPP Library
  3. Copyright (C) 2010 Nathanael C. Fritz
  4. This file is part of SleekXMPP.
  5. See the file LICENSE for copying permission.
  6. """
  7. from sleekxmpp.stanza.rootstanza import RootStanza
  8. from sleekxmpp.xmlstream import StanzaBase, ET
  9. from sleekxmpp.xmlstream.handler import Waiter, Callback
  10. from sleekxmpp.xmlstream.matcher import MatchIDSender, MatcherId
  11. from sleekxmpp.exceptions import IqTimeout, IqError
  12. class Iq(RootStanza):
  13. """
  14. XMPP <iq> stanzas, or info/query stanzas, are XMPP's method of
  15. requesting and modifying information, similar to HTTP's GET and
  16. POST methods.
  17. Each <iq> stanza must have an 'id' value which associates the
  18. stanza with the response stanza. XMPP entities must always
  19. be given a response <iq> stanza with a type of 'result' after
  20. sending a stanza of type 'get' or 'set'.
  21. Most uses cases for <iq> stanzas will involve adding a <query>
  22. element whose namespace indicates the type of information
  23. desired. However, some custom XMPP applications use <iq> stanzas
  24. as a carrier stanza for an application-specific protocol instead.
  25. Example <iq> Stanzas:
  26. <iq to="user@example.com" type="get" id="314">
  27. <query xmlns="http://jabber.org/protocol/disco#items" />
  28. </iq>
  29. <iq to="user@localhost" type="result" id="17">
  30. <query xmlns='jabber:iq:roster'>
  31. <item jid='otheruser@example.net'
  32. name='John Doe'
  33. subscription='both'>
  34. <group>Friends</group>
  35. </item>
  36. </query>
  37. </iq>
  38. Stanza Interface:
  39. query -- The namespace of the <query> element if one exists.
  40. Attributes:
  41. types -- May be one of: get, set, result, or error.
  42. Methods:
  43. __init__ -- Overrides StanzaBase.__init__.
  44. unhandled -- Send error if there are no handlers.
  45. set_payload -- Overrides StanzaBase.set_payload.
  46. set_query -- Add or modify a <query> element.
  47. get_query -- Return the namespace of the <query> element.
  48. del_query -- Remove the <query> element.
  49. reply -- Overrides StanzaBase.reply
  50. send -- Overrides StanzaBase.send
  51. """
  52. namespace = 'jabber:client'
  53. name = 'iq'
  54. interfaces = set(('type', 'to', 'from', 'id', 'query'))
  55. types = set(('get', 'result', 'set', 'error'))
  56. plugin_attrib = name
  57. def __init__(self, *args, **kwargs):
  58. """
  59. Initialize a new <iq> stanza with an 'id' value.
  60. Overrides StanzaBase.__init__.
  61. """
  62. StanzaBase.__init__(self, *args, **kwargs)
  63. if self['id'] == '':
  64. if self.stream is not None:
  65. self['id'] = self.stream.new_id()
  66. else:
  67. self['id'] = '0'
  68. def unhandled(self):
  69. """
  70. Send a feature-not-implemented error if the stanza is not handled.
  71. Overrides StanzaBase.unhandled.
  72. """
  73. if self['type'] in ('get', 'set'):
  74. self.reply()
  75. self['error']['condition'] = 'feature-not-implemented'
  76. self['error']['text'] = 'No handlers registered for this request.'
  77. self.send()
  78. def set_payload(self, value):
  79. """
  80. Set the XML contents of the <iq> stanza.
  81. Arguments:
  82. value -- An XML object to use as the <iq> stanza's contents
  83. """
  84. self.clear()
  85. StanzaBase.set_payload(self, value)
  86. return self
  87. def set_query(self, value):
  88. """
  89. Add or modify a <query> element.
  90. Query elements are differentiated by their namespace.
  91. Arguments:
  92. value -- The namespace of the <query> element.
  93. """
  94. query = self.xml.find("{%s}query" % value)
  95. if query is None and value:
  96. plugin = self.plugin_tag_map.get('{%s}query' % value, None)
  97. if plugin:
  98. self.enable(plugin.plugin_attrib)
  99. else:
  100. self.clear()
  101. query = ET.Element("{%s}query" % value)
  102. self.xml.append(query)
  103. return self
  104. def get_query(self):
  105. """Return the namespace of the <query> element."""
  106. for child in self.xml:
  107. if child.tag.endswith('query'):
  108. ns = child.tag.split('}')[0]
  109. if '{' in ns:
  110. ns = ns[1:]
  111. return ns
  112. return ''
  113. def del_query(self):
  114. """Remove the <query> element."""
  115. for child in self.xml:
  116. if child.tag.endswith('query'):
  117. self.xml.remove(child)
  118. return self
  119. def reply(self, clear=True):
  120. """
  121. Send a reply <iq> stanza.
  122. Overrides StanzaBase.reply
  123. Sets the 'type' to 'result' in addition to the default
  124. StanzaBase.reply behavior.
  125. Arguments:
  126. clear -- Indicates if existing content should be
  127. removed before replying. Defaults to True.
  128. """
  129. self['type'] = 'result'
  130. StanzaBase.reply(self, clear)
  131. return self
  132. def send(self, block=True, timeout=None, callback=None, now=False, timeout_callback=None):
  133. """
  134. Send an <iq> stanza over the XML stream.
  135. The send call can optionally block until a response is received or
  136. a timeout occurs. Be aware that using blocking in non-threaded event
  137. handlers can drastically impact performance. Otherwise, a callback
  138. handler can be provided that will be executed when the Iq stanza's
  139. result reply is received. Be aware though that that the callback
  140. handler will not be executed in its own thread.
  141. Using both block and callback is not recommended, and only the
  142. callback argument will be used in that case.
  143. Overrides StanzaBase.send
  144. Arguments:
  145. block -- Specify if the send call will block until a response
  146. is received, or a timeout occurs. Defaults to True.
  147. timeout -- The length of time (in seconds) to wait for a response
  148. before exiting the send call if blocking is used.
  149. Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
  150. callback -- Optional reference to a stream handler function. Will
  151. be executed when a reply stanza is received.
  152. now -- Indicates if the send queue should be skipped and send
  153. the stanza immediately. Used during stream
  154. initialization. Defaults to False.
  155. timeout_callback -- Optional reference to a stream handler function.
  156. Will be executed when the timeout expires before a
  157. response has been received with the originally-sent IQ
  158. stanza. Only called if there is a callback parameter
  159. (and therefore are in async mode).
  160. """
  161. if timeout is None:
  162. timeout = self.stream.response_timeout
  163. if self.stream.session_bind_event.is_set():
  164. matcher = MatchIDSender({
  165. 'id': self['id'],
  166. 'self': self.stream.boundjid,
  167. 'peer': self['to']
  168. })
  169. else:
  170. matcher = MatcherId(self['id'])
  171. if callback is not None and self['type'] in ('get', 'set'):
  172. handler_name = 'IqCallback_%s' % self['id']
  173. if timeout_callback:
  174. self.callback = callback
  175. self.timeout_callback = timeout_callback
  176. self.stream.schedule('IqTimeout_%s' % self['id'],
  177. timeout,
  178. self._fire_timeout,
  179. repeat=False)
  180. handler = Callback(handler_name,
  181. matcher,
  182. self._handle_result,
  183. once=True)
  184. else:
  185. handler = Callback(handler_name,
  186. matcher,
  187. callback,
  188. once=True)
  189. self.stream.register_handler(handler)
  190. StanzaBase.send(self, now=now)
  191. return handler_name
  192. elif block and self['type'] in ('get', 'set'):
  193. waitfor = Waiter('IqWait_%s' % self['id'], matcher)
  194. self.stream.register_handler(waitfor)
  195. StanzaBase.send(self, now=now)
  196. result = waitfor.wait(timeout)
  197. if not result:
  198. raise IqTimeout(self)
  199. if result['type'] == 'error':
  200. raise IqError(result)
  201. return result
  202. else:
  203. return StanzaBase.send(self, now=now)
  204. def _handle_result(self, iq):
  205. # we got the IQ, so don't fire the timeout
  206. self.stream.scheduler.remove('IqTimeout_%s' % self['id'])
  207. self.callback(iq)
  208. def _fire_timeout(self):
  209. # don't fire the handler for the IQ, if it finally does come in
  210. self.stream.remove_handler('IqCallback_%s' % self['id'])
  211. self.timeout_callback(self)
  212. def _set_stanza_values(self, values):
  213. """
  214. Set multiple stanza interface values using a dictionary.
  215. Stanza plugin values may be set usind nested dictionaries.
  216. If the interface 'query' is given, then it will be set
  217. last to avoid duplication of the <query /> element.
  218. Overrides ElementBase._set_stanza_values.
  219. Arguments:
  220. values -- A dictionary mapping stanza interface with values.
  221. Plugin interfaces may accept a nested dictionary that
  222. will be used recursively.
  223. """
  224. query = values.get('query', '')
  225. if query:
  226. del values['query']
  227. StanzaBase._set_stanza_values(self, values)
  228. self['query'] = query
  229. else:
  230. StanzaBase._set_stanza_values(self, values)
  231. return self
  232. # To comply with PEP8, method names now use underscores.
  233. # Deprecated method names are re-mapped for backwards compatibility.
  234. Iq.setPayload = Iq.set_payload
  235. Iq.getQuery = Iq.get_query
  236. Iq.setQuery = Iq.set_query
  237. Iq.delQuery = Iq.del_query