PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/pyxmpp2/error.py

http://github.com/Jajcus/pyxmpp2
Python | 439 lines | 321 code | 23 blank | 95 comment | 25 complexity | b3375908f16a41392d2ae19448c2eb0d MD5 | raw file
Possible License(s): LGPL-2.1
  1. #
  2. # (C) Copyright 2003-2011 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 6120 <http://xmpp.org/rfcs/rfc6120.html>`__
  20. - `RFC 3920 <http://xmpp.org/rfcs/rfc3920.html>`__
  21. """
  22. from __future__ import absolute_import, division
  23. __docformat__ = "restructuredtext en"
  24. import logging
  25. from .etree import ElementTree, ElementClass
  26. from copy import deepcopy
  27. from .constants import STANZA_ERROR_NS, STREAM_ERROR_NS
  28. from .constants import STREAM_QNP, STANZA_ERROR_QNP, STREAM_ERROR_QNP
  29. from .constants import STANZA_CLIENT_QNP, STANZA_SERVER_QNP, STANZA_NAMESPACES
  30. from .constants import XML_LANG_QNAME
  31. from .xmppserializer import serialize
  32. logger = logging.getLogger("pyxmpp2.error")
  33. STREAM_ERRORS = {
  34. u"bad-format":
  35. ("Received XML cannot be processed",),
  36. u"bad-namespace-prefix":
  37. ("Bad namespace prefix",),
  38. u"conflict":
  39. ("Closing stream because of conflicting stream being opened",),
  40. u"connection-timeout":
  41. ("Connection was idle too long",),
  42. u"host-gone":
  43. ("Hostname is no longer hosted on the server",),
  44. u"host-unknown":
  45. ("Hostname requested is not known to the server",),
  46. u"improper-addressing":
  47. ("Improper addressing",),
  48. u"internal-server-error":
  49. ("Internal server error",),
  50. u"invalid-from":
  51. ("Invalid sender address",),
  52. u"invalid-namespace":
  53. ("Invalid namespace",),
  54. u"invalid-xml":
  55. ("Invalid XML",),
  56. u"not-authorized":
  57. ("Not authorized",),
  58. u"not-well-formed":
  59. ("XML sent by client is not well formed",),
  60. u"policy-violation":
  61. ("Local policy violation",),
  62. u"remote-connection-failed":
  63. ("Remote connection failed",),
  64. u"reset":
  65. ("Stream reset",),
  66. u"resource-constraint":
  67. ("Remote connection failed",),
  68. u"restricted-xml":
  69. ("Restricted XML received",),
  70. u"see-other-host":
  71. ("Redirection required",),
  72. u"system-shutdown":
  73. ("The server is being shut down",),
  74. u"undefined-condition":
  75. ("Unknown error",),
  76. u"unsupported-encoding":
  77. ("Unsupported encoding",),
  78. u"unsupported-feature":
  79. ("Unsupported feature",),
  80. u"unsupported-stanza-type":
  81. ("Unsupported stanza type",),
  82. u"unsupported-version":
  83. ("Unsupported protocol version",),
  84. }
  85. STREAM_ERRORS_Q = dict([( "{{{0}}}{1}".format(STREAM_ERROR_NS, x[0]), x[1])
  86. for x in STREAM_ERRORS.items()])
  87. UNDEFINED_STREAM_CONDITION = \
  88. "{urn:ietf:params:xml:ns:xmpp-streams}undefined-condition"
  89. UNDEFINED_STANZA_CONDITION = \
  90. "{urn:ietf:params:xml:ns:xmpp-stanzas}undefined-condition"
  91. STANZA_ERRORS = {
  92. u"bad-request":
  93. ("Bad request",
  94. "modify"),
  95. u"conflict":
  96. ("Named session or resource already exists",
  97. "cancel"),
  98. u"feature-not-implemented":
  99. ("Feature requested is not implemented",
  100. "cancel"),
  101. u"forbidden":
  102. ("You are forbidden to perform requested action",
  103. "auth"),
  104. u"gone":
  105. ("Recipient or server can no longer be contacted"
  106. " at this address",
  107. "modify"),
  108. u"internal-server-error":
  109. ("Internal server error",
  110. "wait"),
  111. u"item-not-found":
  112. ("Item not found"
  113. ,"cancel"),
  114. u"jid-malformed":
  115. ("JID malformed",
  116. "modify"),
  117. u"not-acceptable":
  118. ("Requested action is not acceptable",
  119. "modify"),
  120. u"not-allowed":
  121. ("Requested action is not allowed",
  122. "cancel"),
  123. u"not-authorized":
  124. ("Not authorized",
  125. "auth"),
  126. u"policy-violation":
  127. ("Policy violation",
  128. "cancel"),
  129. u"recipient-unavailable":
  130. ("Recipient is not available",
  131. "wait"),
  132. u"redirect":
  133. ("Redirection",
  134. "modify"),
  135. u"registration-required":
  136. ("Registration required",
  137. "auth"),
  138. u"remote-server-not-found":
  139. ("Remote server not found",
  140. "cancel"),
  141. u"remote-server-timeout":
  142. ("Remote server timeout",
  143. "wait"),
  144. u"resource-constraint":
  145. ("Resource constraint",
  146. "wait"),
  147. u"service-unavailable":
  148. ("Service is not available",
  149. "cancel"),
  150. u"subscription-required":
  151. ("Subscription is required",
  152. "auth"),
  153. u"undefined-condition":
  154. ("Unknown error",
  155. "cancel"),
  156. u"unexpected-request":
  157. ("Unexpected request",
  158. "wait"),
  159. }
  160. STANZA_ERRORS_Q = dict([( "{{{0}}}{1}".format(STANZA_ERROR_NS, x[0]), x[1])
  161. for x in STANZA_ERRORS.items()])
  162. OBSOLETE_CONDITIONS = {
  163. # changed between RFC 3920 and RFC 6120
  164. "{urn:ietf:params:xml:ns:xmpp-streams}xml-not-well-formed":
  165. "{urn:ietf:params:xml:ns:xmpp-streams}not-well-formed",
  166. "{urn:ietf:params:xml:ns:xmpp-streams}invalid-id":
  167. UNDEFINED_STREAM_CONDITION,
  168. "{urn:ietf:params:xml:ns:xmpp-stanzas}payment-required":
  169. UNDEFINED_STANZA_CONDITION,
  170. }
  171. class ErrorElement(object):
  172. """Base class for both XMPP stream and stanza errors
  173. :Ivariables:
  174. - `condition`: the condition element
  175. - `text`: human-readable error description
  176. - `custom_condition`: list of custom condition elements
  177. - `language`: xml:lang of the error element
  178. :Types:
  179. - `condition_name`: `unicode`
  180. - `condition`: `unicode`
  181. - `text`: `unicode`
  182. - `custom_condition`: `list` of :etree:`ElementTree.Element`
  183. - `language`: `unicode`
  184. """
  185. error_qname = "{unknown}error"
  186. text_qname = "{unknown}text"
  187. cond_qname_prefix = "{unknown}"
  188. def __init__(self, element_or_cond, text = None, language = None):
  189. """Initialize an StanzaErrorElement object.
  190. :Parameters:
  191. - `element_or_cond`: XML <error/> element to decode or an error
  192. condition name or element.
  193. - `text`: optional description to override the default one
  194. - `language`: RFC 3066 language tag for the description
  195. :Types:
  196. - `element_or_cond`: :etree:`ElementTree.Element` or `unicode`
  197. - `text`: `unicode`
  198. - `language`: `unicode`
  199. """
  200. self.text = None
  201. self.custom_condition = []
  202. self.language = language
  203. if isinstance(element_or_cond, basestring):
  204. self.condition = ElementTree.Element(self.cond_qname_prefix
  205. + element_or_cond)
  206. elif not isinstance(element_or_cond, ElementClass):
  207. raise TypeError, "Element or unicode string expected"
  208. else:
  209. self._from_xml(element_or_cond)
  210. if text:
  211. self.text = text
  212. def _from_xml(self, element):
  213. """Initialize an ErrorElement object from an XML element.
  214. :Parameters:
  215. - `element`: XML element to be decoded.
  216. :Types:
  217. - `element`: :etree:`ElementTree.Element`
  218. """
  219. # pylint: disable-msg=R0912
  220. if element.tag != self.error_qname:
  221. raise ValueError(u"{0!r} is not a {1!r} element".format(
  222. element, self.error_qname))
  223. lang = element.get(XML_LANG_QNAME, None)
  224. if lang:
  225. self.language = lang
  226. self.condition = None
  227. for child in element:
  228. if child.tag.startswith(self.cond_qname_prefix):
  229. if self.condition is not None:
  230. logger.warning("Multiple conditions in XMPP error element.")
  231. continue
  232. self.condition = deepcopy(child)
  233. elif child.tag == self.text_qname:
  234. lang = child.get(XML_LANG_QNAME, None)
  235. if lang:
  236. self.language = lang
  237. self.text = child.text.strip()
  238. else:
  239. bad = False
  240. for prefix in (STREAM_QNP, STANZA_CLIENT_QNP, STANZA_SERVER_QNP,
  241. STANZA_ERROR_QNP, STREAM_ERROR_QNP):
  242. if child.tag.startswith(prefix):
  243. logger.warning("Unexpected stream-namespaced"
  244. " element in error.")
  245. bad = True
  246. break
  247. if not bad:
  248. self.custom_condition.append( deepcopy(child) )
  249. if self.condition is None:
  250. self.condition = ElementTree.Element(self.cond_qname_prefix
  251. + "undefined-condition")
  252. if self.condition.tag in OBSOLETE_CONDITIONS:
  253. new_cond_name = OBSOLETE_CONDITIONS[self.condition.tag]
  254. self.condition = ElementTree.Element(new_cond_name)
  255. @property
  256. def condition_name(self):
  257. """Return the condition name (condition element name without the
  258. namespace)."""
  259. return self.condition.tag.split("}", 1)[1]
  260. def add_custom_condition(self, element):
  261. """Add custom condition element to the error.
  262. :Parameters:
  263. - `element`: XML element
  264. :Types:
  265. - `element`: :etree:`ElementTree.Element`
  266. """
  267. self.custom_condition.append(element)
  268. def serialize(self):
  269. """Serialize the stanza into a Unicode XML string.
  270. :return: serialized element.
  271. :returntype: `unicode`"""
  272. return serialize(self.as_xml())
  273. def as_xml(self):
  274. """Return the XML error representation.
  275. :returntype: :etree:`ElementTree.Element`"""
  276. result = ElementTree.Element(self.error_qname)
  277. result.append(deepcopy(self.condition))
  278. if self.text:
  279. text = ElementTree.SubElement(result, self.text_qname)
  280. if self.language:
  281. text.set(XML_LANG_QNAME, self.language)
  282. text.text = self.text
  283. return result
  284. class StreamErrorElement(ErrorElement):
  285. """Stream error element."""
  286. error_qname = STREAM_QNP + "error"
  287. text_qname = STREAM_QNP + "text"
  288. cond_qname_prefix = STREAM_ERROR_QNP
  289. def __init__(self, element_or_cond, text = None, language = None):
  290. """Initialize an StreamErrorElement object.
  291. :Parameters:
  292. - `element_or_cond`: XML <error/> element to decode or an error
  293. condition name or element.
  294. - `text`: optional description to override the default one
  295. - `language`: RFC 3066 language tag for the description
  296. :Types:
  297. - `element_or_cond`: :etree:`ElementTree.Element` or `unicode`
  298. - `text`: `unicode`
  299. - `language`: `unicode`
  300. """
  301. if isinstance(element_or_cond, unicode):
  302. if element_or_cond not in STREAM_ERRORS:
  303. raise ValueError("Bad error condition")
  304. ErrorElement.__init__(self, element_or_cond, text, language)
  305. def get_message(self):
  306. """Get the standard English message for the error.
  307. :return: the error message.
  308. :returntype: `unicode`"""
  309. cond = self.condition_name
  310. if cond in STREAM_ERRORS:
  311. return STREAM_ERRORS[cond][0]
  312. else:
  313. return None
  314. class StanzaErrorElement(ErrorElement):
  315. """Stanza error element.
  316. :Ivariables:
  317. - `error_type`: 'type' of the error, one of: 'auth', 'cancel',
  318. 'continue', 'modify', 'wait'
  319. :Types:
  320. - `error_type`: `unicode`
  321. """
  322. error_qname = STANZA_CLIENT_QNP + "error"
  323. text_qname = STANZA_CLIENT_QNP + "text"
  324. cond_qname_prefix = STANZA_ERROR_QNP
  325. def __init__(self, element_or_cond, text = None, language = None,
  326. error_type = None):
  327. """Initialize an StanzaErrorElement object.
  328. :Parameters:
  329. - `element_or_cond`: XML <error/> element to decode or an error
  330. condition name or element.
  331. - `text`: optional description to override the default one
  332. - `language`: RFC 3066 language tag for the description
  333. - `error_type`: 'type' of the error, one of: 'auth', 'cancel',
  334. 'continue', 'modify', 'wait'
  335. :Types:
  336. - `element_or_cond`: :etree:`ElementTree.Element` or `unicode`
  337. - `text`: `unicode`
  338. - `language`: `unicode`
  339. - `error_type`: `unicode`
  340. """
  341. self.error_type = None
  342. if isinstance(element_or_cond, basestring):
  343. if element_or_cond not in STANZA_ERRORS:
  344. raise ValueError(u"Bad error condition")
  345. elif element_or_cond.tag.startswith(u"{"):
  346. namespace = element_or_cond.tag[1:].split(u"}", 1)[0]
  347. if namespace not in STANZA_NAMESPACES:
  348. raise ValueError(u"Bad error namespace {0!r}".format(namespace))
  349. self.error_qname = u"{{{0}}}error".format(namespace)
  350. self.text_qname = u"{{{0}}}text".format(namespace)
  351. else:
  352. raise ValueError(u"Bad error namespace - no namespace")
  353. ErrorElement.__init__(self, element_or_cond, text, language)
  354. if error_type is not None:
  355. self.error_type = error_type
  356. if self.condition.tag in STANZA_ERRORS_Q:
  357. cond = self.condition.tag
  358. else:
  359. cond = UNDEFINED_STANZA_CONDITION
  360. if not self.error_type:
  361. self.error_type = STANZA_ERRORS_Q[cond][1]
  362. def _from_xml(self, element):
  363. """Initialize an ErrorElement object from an XML element.
  364. :Parameters:
  365. - `element`: XML element to be decoded.
  366. :Types:
  367. - `element`: :etree:`ElementTree.Element`
  368. """
  369. ErrorElement._from_xml(self, element)
  370. error_type = element.get(u"type")
  371. if error_type:
  372. self.error_type = error_type
  373. def get_message(self):
  374. """Get the standard English message for the error.
  375. :return: the error message.
  376. :returntype: `unicode`"""
  377. cond = self.condition_name
  378. if cond in STANZA_ERRORS:
  379. return STANZA_ERRORS[cond][0]
  380. else:
  381. return None
  382. def as_xml(self, stanza_namespace = None): # pylint: disable-msg=W0221
  383. """Return the XML error representation.
  384. :Parameters:
  385. - `stanza_namespace`: namespace URI of the containing stanza
  386. :Types:
  387. - `stanza_namespace`: `unicode`
  388. :returntype: :etree:`ElementTree.Element`"""
  389. if stanza_namespace:
  390. self.error_qname = "{{{0}}}error".format(stanza_namespace)
  391. self.text_qname = "{{{0}}}text".format(stanza_namespace)
  392. result = ErrorElement.as_xml(self)
  393. result.set("type", self.error_type)
  394. return result
  395. # vi: sts=4 et sw=4