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 684 2009-01-17 17:53:01Z 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 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          else: 
291              self.xmlnode.setContent(content) 
 292   
293 -    def add_content(self,content): 
 294          """Add an XML node to the stanza's payload. 
295   
296          :Parameters: 
297              - `content`: XML node to be added to the payload. 
298          :Types: 
299              - `content`: `libxml2.xmlNode`, UTF-8 `str` or an object with 
300                "as_xml()" method. 
301          """ 
302          if hasattr(content, "as_xml"): 
303              content.as_xml(parent = self.xmlnode, doc = common_doc) 
304          elif isinstance(content,libxml2.xmlNode): 
305              self.xmlnode.addChild(content.docCopyNode(common_doc,1)) 
306          else: 
307              self.xmlnode.addContent(content) 
 308   
309 -    def set_new_content(self,ns_uri,name): 
 310          """Set stanza payload to a new XML element. 
311   
312          :Parameters: 
313              - `ns_uri`: XML namespace URI of the element. 
314              - `name`: element name. 
315          :Types: 
316              - `ns_uri`: `str` 
317              - `name`: `str` or `unicode` 
318          """ 
319          while self.xmlnode.children: 
320              self.xmlnode.children.unlinkNode() 
321          return self.add_new_content(ns_uri,name) 
 322   
323 -    def add_new_content(self,ns_uri,name): 
 324          """Add a new XML element to the stanza payload. 
325   
326          :Parameters: 
327              - `ns_uri`: XML namespace URI of the element. 
328              - `name`: element name. 
329          :Types: 
330              - `ns_uri`: `str` 
331              - `name`: `str` or `unicode` 
332          """ 
333          c=self.xmlnode.newChild(None,to_utf8(name),None) 
334          if ns_uri: 
335              ns=c.newNs(ns_uri,None) 
336              c.setNs(ns) 
337          return c 
 338   
340          """Evaluate an XPath expression on the stanza XML node. 
341   
342          The expression will be evaluated in context where the common namespace 
343          (the one used for stanza elements, mapped to 'jabber:client', 
344          'jabber:server', etc.) is bound to prefix "ns" and other namespaces are 
345          bound accordingly to the `namespaces` list. 
346   
347          :Parameters: 
348              - `expr`: XPath expression. 
349              - `namespaces`: mapping from namespace prefixes to URIs. 
350          :Types: 
351              - `expr`: `unicode` 
352              - `namespaces`: `dict` or other mapping 
353          """ 
354          ctxt = common_doc.xpathNewContext() 
355          ctxt.setContextNode(self.xmlnode) 
356          ctxt.xpathRegisterNs("ns",COMMON_NS) 
357          if namespaces: 
358              for prefix,uri in namespaces.items(): 
359                  ctxt.xpathRegisterNs(unicode(prefix),uri) 
360          ret=ctxt.xpathEval(unicode(expr)) 
361          ctxt.xpathFreeContext() 
362          return ret 
 363   
368   
 373   
374   
375