1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """General XMPP Stanza handling.
19
20 Normative reference:
21 - `RFC 3920 <http://www.ietf.org/rfc/rfc3920.txt>`__
22 """
23
24 __revision__="$Id: stanza.py 714 2010-04-05 10:20:10Z jajcus $"
25 __docformat__="restructuredtext en"
26
27 import libxml2
28 import random
29
30 from pyxmpp import xmlextra
31 from pyxmpp.utils import from_utf8,to_utf8
32 from pyxmpp.jid import JID
33 from pyxmpp.xmlextra import common_doc, common_ns, COMMON_NS
34 from pyxmpp.exceptions import ProtocolError, JIDMalformedProtocolError
35
36 random.seed()
37 last_id=random.randrange(1000000)
38
40 """Generate stanza id unique for the session.
41
42 :return: the new id."""
43 global last_id
44 last_id+=1
45 return str(last_id)
46
48 """Base class for all XMPP stanzas.
49
50 :Ivariables:
51 - `xmlnode`: stanza XML node.
52 - `_error`: `pyxmpp.error.StanzaErrorNode` describing the error associated with
53 the stanza of type "error".
54 - `stream`: stream on which the stanza was received or `None`. May be
55 used to send replies or get some session-related parameters.
56 :Types:
57 - `xmlnode`: `libxml2.xmlNode`
58 - `_error`: `pyxmpp.error.StanzaErrorNode`"""
59 stanza_type="Unknown"
60
61 - def __init__(self, name_or_xmlnode, from_jid=None, to_jid=None,
62 stanza_type=None, stanza_id=None, error=None, error_cond=None,
63 stream = None):
64 """Initialize a Stanza object.
65
66 :Parameters:
67 - `name_or_xmlnode`: XML node to be wrapped into the Stanza object
68 or other Presence object to be copied. If not given then new
69 presence stanza is created using following parameters.
70 - `from_jid`: sender JID.
71 - `to_jid`: recipient JID.
72 - `stanza_type`: staza type: one of: "get", "set", "result" or "error".
73 - `stanza_id`: stanza id -- value of stanza's "id" attribute. If
74 not given, then unique for the session value is generated.
75 - `error`: error object. Ignored if `stanza_type` is not "error".
76 - `error_cond`: error condition name. Ignored if `stanza_type` is not
77 "error" or `error` is not None.
78 :Types:
79 - `name_or_xmlnode`: `unicode` or `libxml2.xmlNode` or `Stanza`
80 - `from_jid`: `JID`
81 - `to_jid`: `JID`
82 - `stanza_type`: `unicode`
83 - `stanza_id`: `unicode`
84 - `error`: `pyxmpp.error.StanzaErrorNode`
85 - `error_cond`: `unicode`"""
86 self._error=None
87 self.xmlnode=None
88 if isinstance(name_or_xmlnode,Stanza):
89 self.xmlnode=name_or_xmlnode.xmlnode.docCopyNode(common_doc, True)
90 common_doc.addChild(self.xmlnode)
91 self.xmlnode.reconciliateNs(common_doc)
92 elif isinstance(name_or_xmlnode,libxml2.xmlNode):
93 self.xmlnode=name_or_xmlnode.docCopyNode(common_doc,1)
94 common_doc.addChild(self.xmlnode)
95 try:
96 ns = self.xmlnode.ns()
97 except libxml2.treeError:
98 ns = None
99 if not ns or not ns.name:
100 xmlextra.replace_ns(self.xmlnode, ns, common_ns)
101 self.xmlnode.reconciliateNs(common_doc)
102 else:
103 self.xmlnode=common_doc.newChild(common_ns,name_or_xmlnode,None)
104
105 if from_jid is not None:
106 if not isinstance(from_jid,JID):
107 from_jid=JID(from_jid)
108 self.xmlnode.setProp("from",from_jid.as_utf8())
109
110 if to_jid is not None:
111 if not isinstance(to_jid,JID):
112 to_jid=JID(to_jid)
113 self.xmlnode.setProp("to",to_jid.as_utf8())
114
115 if stanza_type:
116 self.xmlnode.setProp("type",stanza_type)
117
118 if stanza_id:
119 self.xmlnode.setProp("id",stanza_id)
120
121 if self.get_type()=="error":
122 from pyxmpp.error import StanzaErrorNode
123 if error:
124 self._error=StanzaErrorNode(error,parent=self.xmlnode,copy=1)
125 elif error_cond:
126 self._error=StanzaErrorNode(error_cond,parent=self.xmlnode)
127 self.stream = stream
128
130 if self.xmlnode:
131 self.free()
132
134 """Free the node associated with this `Stanza` object."""
135 if self._error:
136 self._error.free_borrowed()
137 self.xmlnode.unlinkNode()
138 self.xmlnode.freeNode()
139 self.xmlnode=None
140
142 """Create a deep copy of the stanza.
143
144 :returntype: `Stanza`"""
145 return Stanza(self)
146
148 """Serialize the stanza into an UTF-8 encoded XML string.
149
150 :return: serialized stanza.
151 :returntype: `str`"""
152 return self.xmlnode.serialize(encoding="utf-8")
153
155 """Return the XML node wrapped into `self`.
156
157 :returntype: `libxml2.xmlNode`"""
158 return self.xmlnode
159
161 """Get "from" attribute of the stanza.
162
163 :return: value of the "from" attribute (sender JID) or None.
164 :returntype: `JID`"""
165 if self.xmlnode.hasProp("from"):
166 try:
167 return JID(from_utf8(self.xmlnode.prop("from")))
168 except JIDError:
169 raise JIDMalformedProtocolError, "Bad JID in the 'from' attribute"
170 else:
171 return None
172
173 get_from_jid=get_from
174
176 """Get "to" attribute of the stanza.
177
178 :return: value of the "to" attribute (recipient JID) or None.
179 :returntype: `JID`"""
180 if self.xmlnode.hasProp("to"):
181 try:
182 return JID(from_utf8(self.xmlnode.prop("to")))
183 except JIDError:
184 raise JIDMalformedProtocolError, "Bad JID in the 'to' attribute"
185 else:
186 return None
187
188 get_to_jid=get_to
189
191 """Get "type" attribute of the stanza.
192
193 :return: value of the "type" attribute (stanza type) or None.
194 :returntype: `unicode`"""
195 if self.xmlnode.hasProp("type"):
196 return from_utf8(self.xmlnode.prop("type"))
197 else:
198 return None
199
200 get_stanza_type=get_type
201
203 """Get "id" attribute of the stanza.
204
205 :return: value of the "id" attribute (stanza identifier) or None.
206 :returntype: `unicode`"""
207 if self.xmlnode.hasProp("id"):
208 return from_utf8(self.xmlnode.prop("id"))
209 else:
210 return None
211
212 get_stanza_id=get_id
213
215 """Get stanza error information.
216
217 :return: object describing the error.
218 :returntype: `pyxmpp.error.StanzaErrorNode`"""
219 if self._error:
220 return self._error
221 n=self.xpath_eval(u"ns:error")
222 if not n:
223 raise ProtocolError, (None, "This stanza contains no error: %r" % (self.serialize(),))
224 from pyxmpp.error import StanzaErrorNode
225 self._error=StanzaErrorNode(n[0],copy=0)
226 return self._error
227
229 """Set "from" attribute of the stanza.
230
231 :Parameters:
232 - `from_jid`: new value of the "from" attribute (sender JID).
233 :Types:
234 - `from_jid`: `JID`"""
235 if from_jid:
236 return self.xmlnode.setProp("from", JID(from_jid).as_utf8())
237 else:
238 return self.xmlnode.unsetProp("from")
239
241 """Set "to" attribute of the stanza.
242
243 :Parameters:
244 - `to_jid`: new value of the "to" attribute (recipient JID).
245 :Types:
246 - `to_jid`: `JID`"""
247 if to_jid:
248 return self.xmlnode.setProp("to", JID(to_jid).as_utf8())
249 else:
250 return self.xmlnode.unsetProp("to")
251
253 """Set "type" attribute of the stanza.
254
255 :Parameters:
256 - `stanza_type`: new value of the "type" attribute (stanza type).
257 :Types:
258 - `stanza_type`: `unicode`"""
259 if stanza_type:
260 return self.xmlnode.setProp("type",to_utf8(stanza_type))
261 else:
262 return self.xmlnode.unsetProp("type")
263
265 """Set "id" attribute of the stanza.
266
267 :Parameters:
268 - `stanza_id`: new value of the "id" attribute (stanza identifier).
269 :Types:
270 - `stanza_id`: `unicode`"""
271 if stanza_id:
272 return self.xmlnode.setProp("id",to_utf8(stanza_id))
273 else:
274 return self.xmlnode.unsetProp("id")
275
276 - def set_content(self,content):
277 """Set stanza content to an XML node.
278
279 :Parameters:
280 - `content`: XML node to be included in the stanza.
281 :Types:
282 - `content`: `libxml2.xmlNode` or unicode, or UTF-8 `str`
283 """
284 while self.xmlnode.children:
285 self.xmlnode.children.unlinkNode()
286 if hasattr(content,"as_xml"):
287 content.as_xml(parent=self.xmlnode,doc=common_doc)
288 elif isinstance(content,libxml2.xmlNode):
289 self.xmlnode.addChild(content.docCopyNode(common_doc,1))
290 elif isinstance(content,unicode):
291 self.xmlnode.setContent(to_utf8(content))
292 else:
293 self.xmlnode.setContent(content)
294
295 - def add_content(self,content):
296 """Add an XML node to the stanza's payload.
297
298 :Parameters:
299 - `content`: XML node to be added to the payload.
300 :Types:
301 - `content`: `libxml2.xmlNode`, UTF-8 `str` or unicode, or
302 an object with "as_xml()" method.
303 """
304 if hasattr(content, "as_xml"):
305 content.as_xml(parent = self.xmlnode, doc = common_doc)
306 elif isinstance(content,libxml2.xmlNode):
307 self.xmlnode.addChild(content.docCopyNode(common_doc,1))
308 elif isinstance(content,unicode):
309 self.xmlnode.addContent(to_utf8(content))
310 else:
311 self.xmlnode.addContent(content)
312
313 - def set_new_content(self,ns_uri,name):
314 """Set stanza payload to a new XML element.
315
316 :Parameters:
317 - `ns_uri`: XML namespace URI of the element.
318 - `name`: element name.
319 :Types:
320 - `ns_uri`: `str`
321 - `name`: `str` or `unicode`
322 """
323 while self.xmlnode.children:
324 self.xmlnode.children.unlinkNode()
325 return self.add_new_content(ns_uri,name)
326
327 - def add_new_content(self,ns_uri,name):
328 """Add a new XML element to the stanza payload.
329
330 :Parameters:
331 - `ns_uri`: XML namespace URI of the element.
332 - `name`: element name.
333 :Types:
334 - `ns_uri`: `str`
335 - `name`: `str` or `unicode`
336 """
337 c=self.xmlnode.newChild(None,to_utf8(name),None)
338 if ns_uri:
339 ns=c.newNs(ns_uri,None)
340 c.setNs(ns)
341 return c
342
344 """Evaluate an XPath expression on the stanza XML node.
345
346 The expression will be evaluated in context where the common namespace
347 (the one used for stanza elements, mapped to 'jabber:client',
348 'jabber:server', etc.) is bound to prefix "ns" and other namespaces are
349 bound accordingly to the `namespaces` list.
350
351 :Parameters:
352 - `expr`: XPath expression.
353 - `namespaces`: mapping from namespace prefixes to URIs.
354 :Types:
355 - `expr`: `unicode`
356 - `namespaces`: `dict` or other mapping
357 """
358 ctxt = common_doc.xpathNewContext()
359 ctxt.setContextNode(self.xmlnode)
360 ctxt.xpathRegisterNs("ns",COMMON_NS)
361 if namespaces:
362 for prefix,uri in namespaces.items():
363 ctxt.xpathRegisterNs(unicode(prefix),uri)
364 ret=ctxt.xpathEval(unicode(expr))
365 ctxt.xpathFreeContext()
366 return ret
367
372
377
378
379