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