1
# -*- test-case-name: twisted.words.test.test_jabbererror -*-
3
# Copyright (c) 2001-2007 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
'remove-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