~cbehrens/nova/lp844160-build-works-with-zones

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/words/protocols/jabber/error.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.words.test.test_jabbererror -*-
 
2
#
 
3
# Copyright (c) 2001-2007 Twisted Matrix Laboratories.
 
4
# See LICENSE for details.
 
5
 
 
6
"""
 
7
XMPP Error support.
 
8
"""
 
9
 
 
10
import copy
 
11
 
 
12
from twisted.words.xish import domish
 
13
 
 
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"
 
17
 
 
18
STANZA_CONDITIONS = {
 
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'},
 
41
}
 
42
 
 
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'),
 
61
}
 
62
 
 
63
class BaseError(Exception):
 
64
    """
 
65
    Base class for XMPP error exceptions.
 
66
 
 
67
    @cvar namespace: The namespace of the C{error} element generated by
 
68
                     C{getElement}.
 
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
 
74
                specific condition.
 
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}.
 
82
    """
 
83
 
 
84
    namespace = None
 
85
 
 
86
    def __init__(self, condition, text=None, textLang=None, appCondition=None):
 
87
        Exception.__init__(self)
 
88
        self.condition = condition
 
89
        self.text = text
 
90
        self.textLang = textLang
 
91
        self.appCondition = appCondition
 
92
 
 
93
 
 
94
    def __str__(self):
 
95
        message = "%s with condition %r" % (self.__class__.__name__,
 
96
                                            self.condition)
 
97
 
 
98
        if self.text:
 
99
            message += ': ' + self.text
 
100
 
 
101
        return message
 
102
 
 
103
 
 
104
    def getElement(self):
 
105
        """
 
106
        Get XML representation from self.
 
107
 
 
108
        The method creates an L{domish} representation of the
 
109
        error data contained in this exception.
 
110
 
 
111
        @rtype: L{domish.Element}
 
112
        """
 
113
        error = domish.Element((None, 'error'))
 
114
        error.addElement((self.namespace, self.condition))
 
115
        if self.text:
 
116
            text = error.addElement((self.namespace, 'text'),
 
117
                                    content=self.text)
 
118
            if self.textLang:
 
119
                text[(NS_XML, 'lang')] = self.textLang
 
120
        if self.appCondition:
 
121
            error.addChild(self.appCondition)
 
122
        return error
 
123
 
 
124
 
 
125
 
 
126
class StreamError(BaseError):
 
127
    """
 
128
    Stream Error exception.
 
129
 
 
130
    Refer to RFC 3920, section 4.7.3, for the allowed values for C{condition}.
 
131
    """
 
132
 
 
133
    namespace = NS_XMPP_STREAMS
 
134
 
 
135
    def getElement(self):
 
136
        """
 
137
        Get XML representation from self.
 
138
 
 
139
        Overrides the base L{BaseError.getElement} to make sure the returned
 
140
        element is in the XML Stream namespace.
 
141
 
 
142
        @rtype: L{domish.Element}
 
143
        """
 
144
        from twisted.words.protocols.jabber.xmlstream import NS_STREAMS
 
145
 
 
146
        error = BaseError.getElement(self)
 
147
        error.uri = NS_STREAMS
 
148
        return error
 
149
 
 
150
 
 
151
 
 
152
class StanzaError(BaseError):
 
153
    """
 
154
    Stanza Error exception.
 
155
 
 
156
    Refer to RFC 3920, section 9.3, for the allowed values for C{condition} and
 
157
    C{type}.
 
158
 
 
159
    @ivar type: The stanza error type. Gives a suggestion to the recipient
 
160
                of the error on how to proceed.
 
161
    @type type: C{str}
 
162
    @ivar code: A numeric identifier for the error condition for backwards
 
163
                compatibility with pre-XMPP Jabber implementations.
 
164
    """
 
165
 
 
166
    namespace = NS_XMPP_STANZAS
 
167
 
 
168
    def __init__(self, condition, type=None, text=None, textLang=None,
 
169
                       appCondition=None):
 
170
        BaseError.__init__(self, condition, text, textLang, appCondition)
 
171
 
 
172
        if type is None:
 
173
            try:
 
174
                type = STANZA_CONDITIONS[condition]['type']
 
175
            except KeyError:
 
176
                pass
 
177
        self.type = type
 
178
 
 
179
        try:
 
180
            self.code = STANZA_CONDITIONS[condition]['code']
 
181
        except KeyError:
 
182
            self.code = None
 
183
 
 
184
        self.children = []
 
185
        self.iq = None
 
186
 
 
187
 
 
188
    def getElement(self):
 
189
        """
 
190
        Get XML representation from self.
 
191
 
 
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}
 
194
        attribute.
 
195
 
 
196
        @rtype: L{domish.Element}
 
197
        """
 
198
        error = BaseError.getElement(self)
 
199
        error['type'] = self.type
 
200
        if self.code:
 
201
            error['code'] = self.code
 
202
        return error
 
203
 
 
204
 
 
205
    def toResponse(self, stanza):
 
206
        """
 
207
        Construct error response stanza.
 
208
 
 
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
 
211
        element.
 
212
 
 
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.
 
217
 
 
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.
 
221
 
 
222
        @param stanza: the stanza to respond to
 
223
        @type stanza: L{domish.Element}
 
224
        """
 
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())
 
229
        return response
 
230
 
 
231
 
 
232
def _getText(element):
 
233
    for child in element.children:
 
234
        if isinstance(child, basestring):
 
235
            return unicode(child)
 
236
 
 
237
    return None
 
238
 
 
239
 
 
240
 
 
241
def _parseError(error, errorNamespace):
 
242
    """
 
243
    Parses an error element.
 
244
 
 
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
 
248
                           condition and text.
 
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.
 
253
    @rtype: L{dict}
 
254
    """
 
255
    condition = None
 
256
    text = None
 
257
    textLang = None
 
258
    appCondition = None
 
259
 
 
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'))
 
265
            else:
 
266
                condition = element.name
 
267
        else:
 
268
            appCondition = element
 
269
 
 
270
    return {
 
271
        'condition': condition,
 
272
        'text': text,
 
273
        'textLang': textLang,
 
274
        'appCondition': appCondition,
 
275
    }
 
276
 
 
277
 
 
278
 
 
279
def exceptionFromStreamError(element):
 
280
    """
 
281
    Build an exception object from a stream error.
 
282
 
 
283
    @param element: the stream error
 
284
    @type element: L{domish.Element}
 
285
    @return: the generated exception object
 
286
    @rtype: L{StreamError}
 
287
    """
 
288
    error = _parseError(element, NS_XMPP_STREAMS)
 
289
 
 
290
    exception = StreamError(error['condition'],
 
291
                            error['text'],
 
292
                            error['textLang'],
 
293
                            error['appCondition'])
 
294
 
 
295
    return exception
 
296
 
 
297
 
 
298
 
 
299
def exceptionFromStanza(stanza):
 
300
    """
 
301
    Build an exception object from an error stanza.
 
302
 
 
303
    @param stanza: the error stanza
 
304
    @type stanza: L{domish.Element}
 
305
    @return: the generated exception object
 
306
    @rtype: L{StanzaError}
 
307
    """
 
308
    children = []
 
309
    condition = text = textLang = appCondition = type = code = None
 
310
 
 
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']
 
317
            text = error['text']
 
318
            textLang = error['textLang']
 
319
            appCondition = error['appCondition']
 
320
 
 
321
            if not condition and code:
 
322
               condition, type = CODES_TO_CONDITIONS[code]
 
323
               text = _getText(stanza.error)
 
324
        else:
 
325
            children.append(element)
 
326
 
 
327
    if condition is None:
 
328
        # TODO: raise exception instead?
 
329
        return StanzaError(None)
 
330
 
 
331
    exception = StanzaError(condition, type, text, textLang, appCondition)
 
332
 
 
333
    exception.children = children
 
334
    exception.stanza = stanza
 
335
 
 
336
    return exception