PageRenderTime 41ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/Twisted-12.1.0/twisted/words/protocols/jabber/error.py

https://bitbucket.org/iparszyk/pyfiscalconnector
Python | 336 lines | 164 code | 55 blank | 117 comment | 24 complexity | 280df26f4e1aa152370ba3b7a0777e40 MD5 | raw file
  1. # -*- test-case-name: twisted.words.test.test_jabbererror -*-
  2. #
  3. # Copyright (c) Twisted Matrix Laboratories.
  4. # See LICENSE for details.
  5. """
  6. XMPP Error support.
  7. """
  8. import copy
  9. from twisted.words.xish import domish
  10. NS_XML = "http://www.w3.org/XML/1998/namespace"
  11. NS_XMPP_STREAMS = "urn:ietf:params:xml:ns:xmpp-streams"
  12. NS_XMPP_STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas"
  13. STANZA_CONDITIONS = {
  14. 'bad-request': {'code': '400', 'type': 'modify'},
  15. 'conflict': {'code': '409', 'type': 'cancel'},
  16. 'feature-not-implemented': {'code': '501', 'type': 'cancel'},
  17. 'forbidden': {'code': '403', 'type': 'auth'},
  18. 'gone': {'code': '302', 'type': 'modify'},
  19. 'internal-server-error': {'code': '500', 'type': 'wait'},
  20. 'item-not-found': {'code': '404', 'type': 'cancel'},
  21. 'jid-malformed': {'code': '400', 'type': 'modify'},
  22. 'not-acceptable': {'code': '406', 'type': 'modify'},
  23. 'not-allowed': {'code': '405', 'type': 'cancel'},
  24. 'not-authorized': {'code': '401', 'type': 'auth'},
  25. 'payment-required': {'code': '402', 'type': 'auth'},
  26. 'recipient-unavailable': {'code': '404', 'type': 'wait'},
  27. 'redirect': {'code': '302', 'type': 'modify'},
  28. 'registration-required': {'code': '407', 'type': 'auth'},
  29. 'remote-server-not-found': {'code': '404', 'type': 'cancel'},
  30. 'remote-server-timeout': {'code': '504', 'type': 'wait'},
  31. 'resource-constraint': {'code': '500', 'type': 'wait'},
  32. 'service-unavailable': {'code': '503', 'type': 'cancel'},
  33. 'subscription-required': {'code': '407', 'type': 'auth'},
  34. 'undefined-condition': {'code': '500', 'type': None},
  35. 'unexpected-request': {'code': '400', 'type': 'wait'},
  36. }
  37. CODES_TO_CONDITIONS = {
  38. '302': ('gone', 'modify'),
  39. '400': ('bad-request', 'modify'),
  40. '401': ('not-authorized', 'auth'),
  41. '402': ('payment-required', 'auth'),
  42. '403': ('forbidden', 'auth'),
  43. '404': ('item-not-found', 'cancel'),
  44. '405': ('not-allowed', 'cancel'),
  45. '406': ('not-acceptable', 'modify'),
  46. '407': ('registration-required', 'auth'),
  47. '408': ('remote-server-timeout', 'wait'),
  48. '409': ('conflict', 'cancel'),
  49. '500': ('internal-server-error', 'wait'),
  50. '501': ('feature-not-implemented', 'cancel'),
  51. '502': ('service-unavailable', 'wait'),
  52. '503': ('service-unavailable', 'cancel'),
  53. '504': ('remote-server-timeout', 'wait'),
  54. '510': ('service-unavailable', 'cancel'),
  55. }
  56. class BaseError(Exception):
  57. """
  58. Base class for XMPP error exceptions.
  59. @cvar namespace: The namespace of the C{error} element generated by
  60. C{getElement}.
  61. @type namespace: C{str}
  62. @ivar condition: The error condition. The valid values are defined by
  63. subclasses of L{BaseError}.
  64. @type contition: C{str}
  65. @ivar text: Optional text message to supplement the condition or application
  66. specific condition.
  67. @type text: C{unicode}
  68. @ivar textLang: Identifier of the language used for the message in C{text}.
  69. Values are as described in RFC 3066.
  70. @type textLang: C{str}
  71. @ivar appCondition: Application specific condition element, supplementing
  72. the error condition in C{condition}.
  73. @type appCondition: object providing L{domish.IElement}.
  74. """
  75. namespace = None
  76. def __init__(self, condition, text=None, textLang=None, appCondition=None):
  77. Exception.__init__(self)
  78. self.condition = condition
  79. self.text = text
  80. self.textLang = textLang
  81. self.appCondition = appCondition
  82. def __str__(self):
  83. message = "%s with condition %r" % (self.__class__.__name__,
  84. self.condition)
  85. if self.text:
  86. message += ': ' + self.text
  87. return message
  88. def getElement(self):
  89. """
  90. Get XML representation from self.
  91. The method creates an L{domish} representation of the
  92. error data contained in this exception.
  93. @rtype: L{domish.Element}
  94. """
  95. error = domish.Element((None, 'error'))
  96. error.addElement((self.namespace, self.condition))
  97. if self.text:
  98. text = error.addElement((self.namespace, 'text'),
  99. content=self.text)
  100. if self.textLang:
  101. text[(NS_XML, 'lang')] = self.textLang
  102. if self.appCondition:
  103. error.addChild(self.appCondition)
  104. return error
  105. class StreamError(BaseError):
  106. """
  107. Stream Error exception.
  108. Refer to RFC 3920, section 4.7.3, for the allowed values for C{condition}.
  109. """
  110. namespace = NS_XMPP_STREAMS
  111. def getElement(self):
  112. """
  113. Get XML representation from self.
  114. Overrides the base L{BaseError.getElement} to make sure the returned
  115. element is in the XML Stream namespace.
  116. @rtype: L{domish.Element}
  117. """
  118. from twisted.words.protocols.jabber.xmlstream import NS_STREAMS
  119. error = BaseError.getElement(self)
  120. error.uri = NS_STREAMS
  121. return error
  122. class StanzaError(BaseError):
  123. """
  124. Stanza Error exception.
  125. Refer to RFC 3920, section 9.3, for the allowed values for C{condition} and
  126. C{type}.
  127. @ivar type: The stanza error type. Gives a suggestion to the recipient
  128. of the error on how to proceed.
  129. @type type: C{str}
  130. @ivar code: A numeric identifier for the error condition for backwards
  131. compatibility with pre-XMPP Jabber implementations.
  132. """
  133. namespace = NS_XMPP_STANZAS
  134. def __init__(self, condition, type=None, text=None, textLang=None,
  135. appCondition=None):
  136. BaseError.__init__(self, condition, text, textLang, appCondition)
  137. if type is None:
  138. try:
  139. type = STANZA_CONDITIONS[condition]['type']
  140. except KeyError:
  141. pass
  142. self.type = type
  143. try:
  144. self.code = STANZA_CONDITIONS[condition]['code']
  145. except KeyError:
  146. self.code = None
  147. self.children = []
  148. self.iq = None
  149. def getElement(self):
  150. """
  151. Get XML representation from self.
  152. Overrides the base L{BaseError.getElement} to make sure the returned
  153. element has a C{type} attribute and optionally a legacy C{code}
  154. attribute.
  155. @rtype: L{domish.Element}
  156. """
  157. error = BaseError.getElement(self)
  158. error['type'] = self.type
  159. if self.code:
  160. error['code'] = self.code
  161. return error
  162. def toResponse(self, stanza):
  163. """
  164. Construct error response stanza.
  165. The C{stanza} is transformed into an error response stanza by
  166. swapping the C{to} and C{from} addresses and inserting an error
  167. element.
  168. @note: This creates a shallow copy of the list of child elements of the
  169. stanza. The child elements themselves are not copied themselves,
  170. and references to their parent element will still point to the
  171. original stanza element.
  172. The serialization of an element does not use the reference to
  173. its parent, so the typical use case of immediately sending out
  174. the constructed error response is not affected.
  175. @param stanza: the stanza to respond to
  176. @type stanza: L{domish.Element}
  177. """
  178. from twisted.words.protocols.jabber.xmlstream import toResponse
  179. response = toResponse(stanza, stanzaType='error')
  180. response.children = copy.copy(stanza.children)
  181. response.addChild(self.getElement())
  182. return response
  183. def _getText(element):
  184. for child in element.children:
  185. if isinstance(child, basestring):
  186. return unicode(child)
  187. return None
  188. def _parseError(error, errorNamespace):
  189. """
  190. Parses an error element.
  191. @param error: The error element to be parsed
  192. @type error: L{domish.Element}
  193. @param errorNamespace: The namespace of the elements that hold the error
  194. condition and text.
  195. @type errorNamespace: C{str}
  196. @return: Dictionary with extracted error information. If present, keys
  197. C{condition}, C{text}, C{textLang} have a string value,
  198. and C{appCondition} has an L{domish.Element} value.
  199. @rtype: C{dict}
  200. """
  201. condition = None
  202. text = None
  203. textLang = None
  204. appCondition = None
  205. for element in error.elements():
  206. if element.uri == errorNamespace:
  207. if element.name == 'text':
  208. text = _getText(element)
  209. textLang = element.getAttribute((NS_XML, 'lang'))
  210. else:
  211. condition = element.name
  212. else:
  213. appCondition = element
  214. return {
  215. 'condition': condition,
  216. 'text': text,
  217. 'textLang': textLang,
  218. 'appCondition': appCondition,
  219. }
  220. def exceptionFromStreamError(element):
  221. """
  222. Build an exception object from a stream error.
  223. @param element: the stream error
  224. @type element: L{domish.Element}
  225. @return: the generated exception object
  226. @rtype: L{StreamError}
  227. """
  228. error = _parseError(element, NS_XMPP_STREAMS)
  229. exception = StreamError(error['condition'],
  230. error['text'],
  231. error['textLang'],
  232. error['appCondition'])
  233. return exception
  234. def exceptionFromStanza(stanza):
  235. """
  236. Build an exception object from an error stanza.
  237. @param stanza: the error stanza
  238. @type stanza: L{domish.Element}
  239. @return: the generated exception object
  240. @rtype: L{StanzaError}
  241. """
  242. children = []
  243. condition = text = textLang = appCondition = type = code = None
  244. for element in stanza.elements():
  245. if element.name == 'error' and element.uri == stanza.uri:
  246. code = element.getAttribute('code')
  247. type = element.getAttribute('type')
  248. error = _parseError(element, NS_XMPP_STANZAS)
  249. condition = error['condition']
  250. text = error['text']
  251. textLang = error['textLang']
  252. appCondition = error['appCondition']
  253. if not condition and code:
  254. condition, type = CODES_TO_CONDITIONS[code]
  255. text = _getText(stanza.error)
  256. else:
  257. children.append(element)
  258. if condition is None:
  259. # TODO: raise exception instead?
  260. return StanzaError(None)
  261. exception = StanzaError(condition, type, text, textLang, appCondition)
  262. exception.children = children
  263. exception.stanza = stanza
  264. return exception