1 # -*- test-case-name: twisted.words.test.test_jabbererror -*-
3 # Copyright (c) Twisted Matrix Laboratories.
4 # See LICENSE for details.
12 from twisted.words.xish import domish
14 NS_XML = "http://www.w3.org/XML/1998/namespace"
15 NS_XMPP_STREAMS = "urn:ietf:params:xml:ns:xmpp-streams"
16 NS_XMPP_STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas"
19 'bad-request': {'code': '400', 'type': 'modify'},
20 'conflict': {'code': '409', 'type': 'cancel'},
21 'feature-not-implemented': {'code': '501', 'type': 'cancel'},
22 'forbidden': {'code': '403', 'type': 'auth'},
23 'gone': {'code': '302', 'type': 'modify'},
24 'internal-server-error': {'code': '500', 'type': 'wait'},
25 'item-not-found': {'code': '404', 'type': 'cancel'},
26 'jid-malformed': {'code': '400', 'type': 'modify'},
27 'not-acceptable': {'code': '406', 'type': 'modify'},
28 'not-allowed': {'code': '405', 'type': 'cancel'},
29 'not-authorized': {'code': '401', 'type': 'auth'},
30 'payment-required': {'code': '402', 'type': 'auth'},
31 'recipient-unavailable': {'code': '404', 'type': 'wait'},
32 'redirect': {'code': '302', 'type': 'modify'},
33 'registration-required': {'code': '407', 'type': 'auth'},
34 'remote-server-not-found': {'code': '404', 'type': 'cancel'},
35 'remote-server-timeout': {'code': '504', 'type': 'wait'},
36 'resource-constraint': {'code': '500', 'type': 'wait'},
37 'service-unavailable': {'code': '503', 'type': 'cancel'},
38 'subscription-required': {'code': '407', 'type': 'auth'},
39 'undefined-condition': {'code': '500', 'type': None},
40 'unexpected-request': {'code': '400', 'type': 'wait'},
43 CODES_TO_CONDITIONS = {
44 '302': ('gone', 'modify'),
45 '400': ('bad-request', 'modify'),
46 '401': ('not-authorized', 'auth'),
47 '402': ('payment-required', 'auth'),
48 '403': ('forbidden', 'auth'),
49 '404': ('item-not-found', 'cancel'),
50 '405': ('not-allowed', 'cancel'),
51 '406': ('not-acceptable', 'modify'),
52 '407': ('registration-required', 'auth'),
53 '408': ('remote-server-timeout', 'wait'),
54 '409': ('conflict', 'cancel'),
55 '500': ('internal-server-error', 'wait'),
56 '501': ('feature-not-implemented', 'cancel'),
57 '502': ('service-unavailable', 'wait'),
58 '503': ('service-unavailable', 'cancel'),
59 '504': ('remote-server-timeout', 'wait'),
60 '510': ('service-unavailable', 'cancel'),
63 class BaseError(Exception):
65 Base class for XMPP error exceptions.
67 @cvar namespace: The namespace of the C{error} element generated by
69 @type namespace: C{str}
70 @ivar condition: The error condition. The valid values are defined by
71 subclasses of L{BaseError}.
72 @type contition: C{str}
73 @ivar text: Optional text message to supplement the condition or application
75 @type text: C{unicode}
76 @ivar textLang: Identifier of the language used for the message in C{text}.
77 Values are as described in RFC 3066.
78 @type textLang: C{str}
79 @ivar appCondition: Application specific condition element, supplementing
80 the error condition in C{condition}.
81 @type appCondition: object providing L{domish.IElement}.
86 def __init__(self, condition, text=None, textLang=None, appCondition=None):
87 Exception.__init__(self)
88 self.condition = condition
90 self.textLang = textLang
91 self.appCondition = appCondition
95 message = "%s with condition %r" % (self.__class__.__name__,
99 message += ': ' + self.text
104 def getElement(self):
106 Get XML representation from self.
108 The method creates an L{domish} representation of the
109 error data contained in this exception.
111 @rtype: L{domish.Element}
113 error = domish.Element((None, 'error'))
114 error.addElement((self.namespace, self.condition))
116 text = error.addElement((self.namespace, 'text'),
119 text[(NS_XML, 'lang')] = self.textLang
120 if self.appCondition:
121 error.addChild(self.appCondition)
126 class StreamError(BaseError):
128 Stream Error exception.
130 Refer to RFC 3920, section 4.7.3, for the allowed values for C{condition}.
133 namespace = NS_XMPP_STREAMS
135 def getElement(self):
137 Get XML representation from self.
139 Overrides the base L{BaseError.getElement} to make sure the returned
140 element is in the XML Stream namespace.
142 @rtype: L{domish.Element}
144 from twisted.words.protocols.jabber.xmlstream import NS_STREAMS
146 error = BaseError.getElement(self)
147 error.uri = NS_STREAMS
152 class StanzaError(BaseError):
154 Stanza Error exception.
156 Refer to RFC 3920, section 9.3, for the allowed values for C{condition} and
159 @ivar type: The stanza error type. Gives a suggestion to the recipient
160 of the error on how to proceed.
162 @ivar code: A numeric identifier for the error condition for backwards
163 compatibility with pre-XMPP Jabber implementations.
166 namespace = NS_XMPP_STANZAS
168 def __init__(self, condition, type=None, text=None, textLang=None,
170 BaseError.__init__(self, condition, text, textLang, appCondition)
174 type = STANZA_CONDITIONS[condition]['type']
180 self.code = STANZA_CONDITIONS[condition]['code']
188 def getElement(self):
190 Get XML representation from self.
192 Overrides the base L{BaseError.getElement} to make sure the returned
193 element has a C{type} attribute and optionally a legacy C{code}
196 @rtype: L{domish.Element}
198 error = BaseError.getElement(self)
199 error['type'] = self.type
201 error['code'] = self.code
205 def toResponse(self, stanza):
207 Construct error response stanza.
209 The C{stanza} is transformed into an error response stanza by
210 swapping the C{to} and C{from} addresses and inserting an error
213 @note: This creates a shallow copy of the list of child elements of the
214 stanza. The child elements themselves are not copied themselves,
215 and references to their parent element will still point to the
216 original stanza element.
218 The serialization of an element does not use the reference to
219 its parent, so the typical use case of immediately sending out
220 the constructed error response is not affected.
222 @param stanza: the stanza to respond to
223 @type stanza: L{domish.Element}
225 from twisted.words.protocols.jabber.xmlstream import toResponse
226 response = toResponse(stanza, stanzaType='error')
227 response.children = copy.copy(stanza.children)
228 response.addChild(self.getElement())
232 def _getText(element):
233 for child in element.children:
234 if isinstance(child, basestring):
235 return unicode(child)
241 def _parseError(error, errorNamespace):
243 Parses an error element.
245 @param error: The error element to be parsed
246 @type error: L{domish.Element}
247 @param errorNamespace: The namespace of the elements that hold the error
249 @type errorNamespace: C{str}
250 @return: Dictionary with extracted error information. If present, keys
251 C{condition}, C{text}, C{textLang} have a string value,
252 and C{appCondition} has an L{domish.Element} value.
260 for element in error.elements():
261 if element.uri == errorNamespace:
262 if element.name == 'text':
263 text = _getText(element)
264 textLang = element.getAttribute((NS_XML, 'lang'))
266 condition = element.name
268 appCondition = element
271 'condition': condition,
273 'textLang': textLang,
274 'appCondition': appCondition,
279 def exceptionFromStreamError(element):
281 Build an exception object from a stream error.
283 @param element: the stream error
284 @type element: L{domish.Element}
285 @return: the generated exception object
286 @rtype: L{StreamError}
288 error = _parseError(element, NS_XMPP_STREAMS)
290 exception = StreamError(error['condition'],
293 error['appCondition'])
299 def exceptionFromStanza(stanza):
301 Build an exception object from an error stanza.
303 @param stanza: the error stanza
304 @type stanza: L{domish.Element}
305 @return: the generated exception object
306 @rtype: L{StanzaError}
309 condition = text = textLang = appCondition = type = code = None
311 for element in stanza.elements():
312 if element.name == 'error' and element.uri == stanza.uri:
313 code = element.getAttribute('code')
314 type = element.getAttribute('type')
315 error = _parseError(element, NS_XMPP_STANZAS)
316 condition = error['condition']
318 textLang = error['textLang']
319 appCondition = error['appCondition']
321 if not condition and code:
322 condition, type = CODES_TO_CONDITIONS[code]
323 text = _getText(stanza.error)
325 children.append(element)
327 if condition is None:
328 # TODO: raise exception instead?
329 return StanzaError(None)
331 exception = StanzaError(condition, type, text, textLang, appCondition)
333 exception.children = children
334 exception.stanza = stanza