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