PageRenderTime 59ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/digsby/lib/pyxmpp/error.py

https://github.com/xiaohangyu/digsby
Python | 538 lines | 472 code | 13 blank | 53 comment | 19 complexity | ee624cedd583435648d7e3cbae12915a MD5 | raw file
Possible License(s): LGPL-2.1, CPL-1.0
  1. #
  2. # (C) Copyright 2003-2010 Jacek Konieczny <jajcus@jajcus.net>
  3. #
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU Lesser General Public License Version
  6. # 2.1 as published by the Free Software Foundation.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU Lesser General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU Lesser General Public
  14. # License along with this program; if not, write to the Free Software
  15. # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  16. #
  17. """XMPP error handling.
  18. Normative reference:
  19. - `RFC 3920 <http://www.ietf.org/rfc/rfc3920.txt>`__
  20. - `JEP 86 <http://www.jabber.org/jeps/jep-0086.html>`__
  21. """
  22. __revision__="$Id: error.py 714 2010-04-05 10:20:10Z jajcus $"
  23. __docformat__="restructuredtext en"
  24. import libxml2
  25. from pyxmpp.utils import from_utf8, to_utf8
  26. from pyxmpp.xmlextra import common_doc, common_root, common_ns
  27. from pyxmpp import xmlextra
  28. from pyxmpp.exceptions import ProtocolError
  29. stream_errors={
  30. u"bad-format":
  31. ("Received XML cannot be processed",),
  32. u"bad-namespace-prefix":
  33. ("Bad namespace prefix",),
  34. u"conflict":
  35. ("Closing stream because of conflicting stream being opened",),
  36. u"connection-timeout":
  37. ("Connection was idle too long",),
  38. u"host-gone":
  39. ("Hostname is no longer hosted on the server",),
  40. u"host-unknown":
  41. ("Hostname requested is not known to the server",),
  42. u"improper-addressing":
  43. ("Improper addressing",),
  44. u"internal-server-error":
  45. ("Internal server error",),
  46. u"invalid-from":
  47. ("Invalid sender address",),
  48. u"invalid-id":
  49. ("Invalid stream ID",),
  50. u"invalid-namespace":
  51. ("Invalid namespace",),
  52. u"invalid-xml":
  53. ("Invalid XML",),
  54. u"not-authorized":
  55. ("Not authorized",),
  56. u"policy-violation":
  57. ("Local policy violation",),
  58. u"remote-connection-failed":
  59. ("Remote connection failed",),
  60. u"resource-constraint":
  61. ("Remote connection failed",),
  62. u"restricted-xml":
  63. ("Restricted XML received",),
  64. u"see-other-host":
  65. ("Redirection required",),
  66. u"system-shutdown":
  67. ("The server is being shut down",),
  68. u"undefined-condition":
  69. ("Unknown error",),
  70. u"unsupported-encoding":
  71. ("Unsupported encoding",),
  72. u"unsupported-stanza-type":
  73. ("Unsupported stanza type",),
  74. u"unsupported-version":
  75. ("Unsupported protocol version",),
  76. u"xml-not-well-formed":
  77. ("XML sent by client is not well formed",),
  78. }
  79. stanza_errors={
  80. u"bad-request":
  81. ("Bad request",
  82. "modify",400),
  83. u"conflict":
  84. ("Named session or resource already exists",
  85. "cancel",409),
  86. u"feature-not-implemented":
  87. ("Feature requested is not implemented",
  88. "cancel",501),
  89. u"forbidden":
  90. ("You are forbidden to perform requested action",
  91. "auth",403),
  92. u"gone":
  93. ("Recipient or server can no longer be contacted at this address",
  94. "modify",302),
  95. u"internal-server-error":
  96. ("Internal server error",
  97. "wait",500),
  98. u"item-not-found":
  99. ("Item not found"
  100. ,"cancel",404),
  101. u"jid-malformed":
  102. ("JID malformed",
  103. "modify",400),
  104. u"not-acceptable":
  105. ("Requested action is not acceptable",
  106. "modify",406),
  107. u"not-allowed":
  108. ("Requested action is not allowed",
  109. "cancel",405),
  110. u"not-authorized":
  111. ("Not authorized",
  112. "auth",401),
  113. u"payment-required":
  114. ("Payment required",
  115. "auth",402),
  116. u"recipient-unavailable":
  117. ("Recipient is not available",
  118. "wait",404),
  119. u"redirect":
  120. ("Redirection",
  121. "modify",302),
  122. u"registration-required":
  123. ("Registration required",
  124. "auth",407),
  125. u"remote-server-not-found":
  126. ("Remote server not found",
  127. "cancel",404),
  128. u"remote-server-timeout":
  129. ("Remote server timeout",
  130. "wait",504),
  131. u"resource-constraint":
  132. ("Resource constraint",
  133. "wait",500),
  134. u"service-unavailable":
  135. ("Service is not available",
  136. "cancel",503),
  137. u"subscription-required":
  138. ("Subscription is required",
  139. "auth",407),
  140. u"undefined-condition":
  141. ("Unknown error",
  142. "cancel",500),
  143. u"unexpected-request":
  144. ("Unexpected request",
  145. "wait",400),
  146. }
  147. legacy_codes={
  148. 302: "redirect",
  149. 400: "bad-request",
  150. 401: "not-authorized",
  151. 402: "payment-required",
  152. 403: "forbidden",
  153. 404: "item-not-found",
  154. 405: "not-allowed",
  155. 406: "not-acceptable",
  156. 407: "registration-required",
  157. 408: "remote-server-timeout",
  158. 409: "conflict",
  159. 500: "internal-server-error",
  160. 501: "feature-not-implemented",
  161. 502: "service-unavailable",
  162. 503: "service-unavailable",
  163. 504: "remote-server-timeout",
  164. 510: "service-unavailable",
  165. }
  166. STANZA_ERROR_NS='urn:ietf:params:xml:ns:xmpp-stanzas'
  167. STREAM_ERROR_NS='urn:ietf:params:xml:ns:xmpp-streams'
  168. PYXMPP_ERROR_NS='http://pyxmpp.jajcus.net/xmlns/errors'
  169. STREAM_NS="http://etherx.jabber.org/streams"
  170. class ErrorNode:
  171. """Base class for both XMPP stream and stanza errors"""
  172. def __init__(self,xmlnode_or_cond,ns=None,copy=True,parent=None):
  173. """Initialize an ErrorNode object.
  174. :Parameters:
  175. - `xmlnode_or_cond`: XML node to be wrapped into this object
  176. or error condition name.
  177. - `ns`: XML namespace URI of the error condition element (to be
  178. used when the provided node has no namespace).
  179. - `copy`: When `True` then the XML node will be copied,
  180. otherwise it is only borrowed.
  181. - `parent`: Parent node for the XML node to be copied or created.
  182. :Types:
  183. - `xmlnode_or_cond`: `libxml2.xmlNode` or `unicode`
  184. - `ns`: `unicode`
  185. - `copy`: `bool`
  186. - `parent`: `libxml2.xmlNode`"""
  187. if type(xmlnode_or_cond) is str:
  188. xmlnode_or_cond=unicode(xmlnode_or_cond,"utf-8")
  189. self.xmlnode=None
  190. self.borrowed=0
  191. if isinstance(xmlnode_or_cond,libxml2.xmlNode):
  192. self.__from_xml(xmlnode_or_cond,ns,copy,parent)
  193. elif isinstance(xmlnode_or_cond,ErrorNode):
  194. if not copy:
  195. raise TypeError, "ErrorNodes may only be copied"
  196. self.ns=from_utf8(xmlnode_or_cond.ns.getContent())
  197. self.xmlnode=xmlnode_or_cond.xmlnode.docCopyNode(common_doc,1)
  198. if not parent:
  199. parent=common_root
  200. parent.addChild(self.xmlnode)
  201. elif ns is None:
  202. raise ValueError, "Condition namespace not given"
  203. else:
  204. if parent:
  205. self.xmlnode=parent.newChild(common_ns,"error",None)
  206. self.borrowed=1
  207. else:
  208. self.xmlnode=common_root.newChild(common_ns,"error",None)
  209. cond=self.xmlnode.newChild(None,to_utf8(xmlnode_or_cond),None)
  210. ns=cond.newNs(ns,None)
  211. cond.setNs(ns)
  212. self.ns=from_utf8(ns.getContent())
  213. def __from_xml(self,xmlnode,ns,copy,parent):
  214. """Initialize an ErrorNode object from an XML node.
  215. :Parameters:
  216. - `xmlnode`: XML node to be wrapped into this object.
  217. - `ns`: XML namespace URI of the error condition element (to be
  218. used when the provided node has no namespace).
  219. - `copy`: When `True` then the XML node will be copied,
  220. otherwise it is only borrowed.
  221. - `parent`: Parent node for the XML node to be copied or created.
  222. :Types:
  223. - `xmlnode`: `libxml2.xmlNode`
  224. - `ns`: `unicode`
  225. - `copy`: `bool`
  226. - `parent`: `libxml2.xmlNode`"""
  227. if not ns:
  228. ns=None
  229. c=xmlnode.children
  230. while c:
  231. ns=c.ns().getContent()
  232. if ns in (STREAM_ERROR_NS,STANZA_ERROR_NS):
  233. break
  234. ns=None
  235. c=c.next
  236. if ns==None:
  237. raise ProtocolError, "Bad error namespace"
  238. self.ns=from_utf8(ns)
  239. if copy:
  240. self.xmlnode=xmlnode.docCopyNode(common_doc,1)
  241. if not parent:
  242. parent=common_root
  243. parent.addChild(self.xmlnode)
  244. else:
  245. self.xmlnode=xmlnode
  246. self.borrowed=1
  247. if copy:
  248. ns1=xmlnode.ns()
  249. xmlextra.replace_ns(self.xmlnode, ns1, common_ns)
  250. def __del__(self):
  251. if self.xmlnode:
  252. self.free()
  253. def free(self):
  254. """Free the associated XML node."""
  255. if not self.borrowed:
  256. self.xmlnode.unlinkNode()
  257. self.xmlnode.freeNode()
  258. self.xmlnode=None
  259. def free_borrowed(self):
  260. """Free the associated "borrowed" XML node."""
  261. self.xmlnode=None
  262. def is_legacy(self):
  263. """Check if the error node is a legacy error element.
  264. :return: `True` if it is a legacy error.
  265. :returntype: `bool`"""
  266. return not self.xmlnode.hasProp("type")
  267. def xpath_eval(self,expr,namespaces=None):
  268. """Evaluate XPath expression on the error element.
  269. The expression will be evaluated in context where the common namespace
  270. (the one used for stanza elements, mapped to 'jabber:client',
  271. 'jabber:server', etc.) is bound to prefix "ns" and other namespaces are
  272. bound accordingly to the `namespaces` list.
  273. :Parameters:
  274. - `expr`: the XPath expression.
  275. - `namespaces`: prefix to namespace mapping.
  276. :Types:
  277. - `expr`: `unicode`
  278. - `namespaces`: `dict`
  279. :return: the result of the expression evaluation.
  280. """
  281. ctxt = common_doc.xpathNewContext()
  282. ctxt.setContextNode(self.xmlnode)
  283. ctxt.xpathRegisterNs("ns",to_utf8(self.ns))
  284. if namespaces:
  285. for prefix,uri in namespaces.items():
  286. ctxt.xpathRegisterNs(prefix,uri)
  287. ret=ctxt.xpathEval(expr)
  288. ctxt.xpathFreeContext()
  289. return ret
  290. def get_condition(self,ns=None):
  291. """Get the condition element of the error.
  292. :Parameters:
  293. - `ns`: namespace URI of the condition element if it is not
  294. the XMPP namespace of the error element.
  295. :Types:
  296. - `ns`: `unicode`
  297. :return: the condition element or `None`.
  298. :returntype: `libxml2.xmlNode`"""
  299. if ns is None:
  300. ns=self.ns
  301. c=self.xpath_eval("ns:*")
  302. if not c:
  303. self.upgrade()
  304. c=self.xpath_eval("ns:*")
  305. if not c:
  306. return None
  307. if ns==self.ns and c[0].name=="text":
  308. if len(c)==1:
  309. return None
  310. c=c[1:]
  311. return c[0]
  312. def get_text(self):
  313. """Get the description text from the error element.
  314. :return: the text provided with the error or `None`.
  315. :returntype: `unicode`"""
  316. c=self.xpath_eval("ns:*")
  317. if not c:
  318. self.upgrade()
  319. t=self.xpath_eval("ns:text")
  320. if not t:
  321. return None
  322. return from_utf8(t[0].getContent())
  323. def add_custom_condition(self,ns,cond,content=None):
  324. """Add custom condition element to the error.
  325. :Parameters:
  326. - `ns`: namespace URI.
  327. - `cond`: condition name.
  328. - `content`: content of the element.
  329. :Types:
  330. - `ns`: `unicode`
  331. - `cond`: `unicode`
  332. - `content`: `unicode`
  333. :return: the new condition element.
  334. :returntype: `libxml2.xmlNode`"""
  335. c=self.xmlnode.newTextChild(None,to_utf8(cond),content)
  336. ns=c.newNs(to_utf8(ns),None)
  337. c.setNs(ns)
  338. return c
  339. def upgrade(self):
  340. """Upgrade a legacy error element to the XMPP compliant one.
  341. Use the error code provided to select the condition and the
  342. <error/> CDATA for the error text."""
  343. if not self.xmlnode.hasProp("code"):
  344. code=None
  345. else:
  346. try:
  347. code=int(self.xmlnode.prop("code"))
  348. except (ValueError,KeyError):
  349. code=None
  350. if code and legacy_codes.has_key(code):
  351. cond=legacy_codes[code]
  352. else:
  353. cond=None
  354. condition=self.xpath_eval("ns:*")
  355. if condition:
  356. return
  357. elif cond is None:
  358. condition=self.xmlnode.newChild(None,"undefined-condition",None)
  359. ns=condition.newNs(to_utf8(self.ns),None)
  360. condition.setNs(ns)
  361. condition=self.xmlnode.newChild(None,"unknown-legacy-error",None)
  362. ns=condition.newNs(PYXMPP_ERROR_NS,None)
  363. condition.setNs(ns)
  364. else:
  365. condition=self.xmlnode.newChild(None,cond,None)
  366. ns=condition.newNs(to_utf8(self.ns),None)
  367. condition.setNs(ns)
  368. txt=self.xmlnode.getContent()
  369. if txt:
  370. text=self.xmlnode.newTextChild(None,"text",txt)
  371. ns=text.newNs(to_utf8(self.ns),None)
  372. text.setNs(ns)
  373. def downgrade(self):
  374. """Downgrade an XMPP error element to the legacy format.
  375. Add a numeric code attribute according to the condition name."""
  376. if self.xmlnode.hasProp("code"):
  377. return
  378. cond=self.get_condition()
  379. if not cond:
  380. return
  381. cond=cond.name
  382. if stanza_errors.has_key(cond) and stanza_errors[cond][2]:
  383. self.xmlnode.setProp("code",to_utf8(stanza_errors[cond][2]))
  384. def serialize(self):
  385. """Serialize the element node.
  386. :return: serialized element in UTF-8 encoding.
  387. :returntype: `str`"""
  388. return self.xmlnode.serialize(encoding="utf-8")
  389. class StreamErrorNode(ErrorNode):
  390. """Stream error element."""
  391. def __init__(self,xmlnode_or_cond,copy=1,parent=None):
  392. """Initialize a StreamErrorNode object.
  393. :Parameters:
  394. - `xmlnode_or_cond`: XML node to be wrapped into this object
  395. or the primary (defined by XMPP specification) error condition name.
  396. - `copy`: When `True` then the XML node will be copied,
  397. otherwise it is only borrowed.
  398. - `parent`: Parent node for the XML node to be copied or created.
  399. :Types:
  400. - `xmlnode_or_cond`: `libxml2.xmlNode` or `unicode`
  401. - `copy`: `bool`
  402. - `parent`: `libxml2.xmlNode`"""
  403. if type(xmlnode_or_cond) is str:
  404. xmlnode_or_cond = xmlnode_or_cond.decode("utf-8")
  405. if type(xmlnode_or_cond) is unicode:
  406. if not stream_errors.has_key(xmlnode_or_cond):
  407. raise ValueError, "Bad error condition"
  408. ErrorNode.__init__(self,xmlnode_or_cond,STREAM_ERROR_NS,copy=copy,parent=parent)
  409. def get_message(self):
  410. """Get the message for the error.
  411. :return: the error message.
  412. :returntype: `unicode`"""
  413. cond=self.get_condition()
  414. if not cond:
  415. self.upgrade()
  416. cond=self.get_condition()
  417. if not cond:
  418. return None
  419. cond=cond.name
  420. if not stream_errors.has_key(cond):
  421. return None
  422. return stream_errors[cond][0]
  423. class StanzaErrorNode(ErrorNode):
  424. """Stanza error element."""
  425. def __init__(self,xmlnode_or_cond,error_type=None,copy=1,parent=None):
  426. """Initialize a StreamErrorNode object.
  427. :Parameters:
  428. - `xmlnode_or_cond`: XML node to be wrapped into this object
  429. or the primary (defined by XMPP specification) error condition name.
  430. - `error_type`: type of the error, one of: 'cancel', 'continue',
  431. 'modify', 'auth', 'wait'.
  432. - `copy`: When `True` then the XML node will be copied,
  433. otherwise it is only borrowed.
  434. - `parent`: Parent node for the XML node to be copied or created.
  435. :Types:
  436. - `xmlnode_or_cond`: `libxml2.xmlNode` or `unicode`
  437. - `error_type`: `unicode`
  438. - `copy`: `bool`
  439. - `parent`: `libxml2.xmlNode`"""
  440. if type(xmlnode_or_cond) is str:
  441. xmlnode_or_cond=unicode(xmlnode_or_cond,"utf-8")
  442. if type(xmlnode_or_cond) is unicode:
  443. if not stanza_errors.has_key(xmlnode_or_cond):
  444. raise ValueError, "Bad error condition"
  445. ErrorNode.__init__(self,xmlnode_or_cond,STANZA_ERROR_NS,copy=copy,parent=parent)
  446. if type(xmlnode_or_cond) is unicode:
  447. if error_type is None:
  448. error_type=stanza_errors[xmlnode_or_cond][1]
  449. self.xmlnode.setProp("type",to_utf8(error_type))
  450. def get_type(self):
  451. """Get the error type.
  452. :return: type of the error.
  453. :returntype: `unicode`"""
  454. if not self.xmlnode.hasProp("type"):
  455. self.upgrade()
  456. return from_utf8(self.xmlnode.prop("type"))
  457. def upgrade(self):
  458. """Upgrade a legacy error element to the XMPP compliant one.
  459. Use the error code provided to select the condition and the
  460. <error/> CDATA for the error text."""
  461. ErrorNode.upgrade(self)
  462. if self.xmlnode.hasProp("type"):
  463. return
  464. cond=self.get_condition().name
  465. if stanza_errors.has_key(cond):
  466. typ=stanza_errors[cond][1]
  467. self.xmlnode.setProp("type",typ)
  468. def get_message(self):
  469. """Get the message for the error.
  470. :return: the error message.
  471. :returntype: `unicode`"""
  472. cond=self.get_condition()
  473. if not cond:
  474. self.upgrade()
  475. cond=self.get_condition()
  476. if not cond:
  477. return None
  478. cond=cond.name
  479. if not stanza_errors.has_key(cond):
  480. return None
  481. return stanza_errors[cond][0]
  482. # vi: sts=4 et sw=4